How to get TemplateHelper to work with UTF-8 specification, EOL standard and read / execute permissions?

What am i trying to do

I am trying to use FAKE TemplateHelperto instantiate some template files that become shell scripts (both Unix and DOS). I did this in the FAKE task as follows:

CopyFile (buildApp @@ product + ".sh") (srcTem @@ "mono-launcher.sh")
processTemplates substs [(buildApp @@ product + ".sh")]

where substsare the var-> value mappings for substitution, and srcTemand buildAppare the source directories of the templates and directories of the output of the assembly, respectively.

What happened wrong

I ran into three problems:

First I TemplateHelper.processTemplateswrote down the UTF-8 specification at the beginning of the generated script. At least on Unix, this will ruin the shebang line and give an error message when running the script. The octal dump of the first line of the source template and the generated file show the appearance of these 3 nasty bytes:

$ head -1 ./src/templates/mono-launcher.sh | od -c
0000000    #   !   /   b   i   n   /   s   h  \n
$ head -1 ./build/app/Hello-Fsharp.sh | od -c
0000000  357 273 277   #   !   /   b   i   n   /   s   h  \n

Secondly, there was no way to say that the generated Unix sh scripts should have Unix EOL in them, while Windows.bat scripts should have EOS DOS.

Thirdly, there was no obvious way to say that the resulting files should have read and execute permissions.

This question is about whether anyone knows how to deal with these problems, at least better than what I came up with below.

TemplateHelper . processTemplate WriteFile, WriteToFile, encoding, EnvironmentHelper System.Text.Encoding.Default, UTF-8. , , , ASCIIEncoding.

... - TemplateHelper , , :

/// <summary>Copy a template file from source to destination, making string substitutions
/// along the way and allowing for different file encodings at each end.</summary>
///
/// <remarks>This only exists because TemplateHelper put UTF-8 BOM at the front, breaking
/// script shebang lines.  The EOL choice here works from Unix but needs testing on
/// Windows (will it double up on returns if the build machine is Window?), and the
/// read/execute permission setting is iffy. </remarks>
///
/// <param name="substs">IDictionary of key, value string replacements to make</param>
/// <param name="fromEncoding">Encoding of fromTemplate</param>
/// <param name="fromTemplate">file with the source template</param>
/// <param name="toEncoding">Encoding of toFile</param>
/// <param name="toFile">file to which the instantiated template gets written</param>
/// <param name="readExecute">whether to mark toFile as readable &amp; executable by all</param>
///
/// <returns>Nothing of interest.</returns>
let processTemplate substs fromEncoding fromTemplate toEncoding toFile unixEOL readExecute =

  let ReadTemplate encoding fileName =                 // Read lines of template file, but lazily:
    File.ReadLines(fileName, encoding)                 //  start processing before entirety is read

  let SubstituteVariables substs =                     // Substitute variables in seq of line strs
    Seq.map (fun (line : string) ->                    // Loop over lines
               substs                                  // Fold in effect of each key, val pair
               |> Seq.fold (fun (sb : StringBuilder) (KeyValue(k : string, v : string)) -> 
                              sb.Replace(k, v))        // Generates new StringBuilder w/each var
                           (new StringBuilder(line))   // Initial accumulator is original line
               |> toText)                              // Convert final StringBuilder back to text

  let MaybeAddReturn =                                 // Add extra return if not Unix EOLs
    Seq.map (fun line -> if unixEOL then line else line + "\r")  // *** Test if works on Windows?

  let WriteTemplate encoding fileName (lines : seq<string>) =
    File.WriteAllLines(fileName, lines, encoding)      // Write out altered lines (eol chosen how?)
    fileName                                           // Return filename for next step in pipeline

  let MaybeSetReadExecute fileName =                   // Platform neutral, mark read + exec perms
    if readExecute then                                // Only do this if asked
      if isMono then                                   // We're running on Mono
        Shell.Exec("chmod", "a+rx " + fileName, here) |> ignore // *** Horrible kludge?
      else                                             // Not under Mono: do it the .Net way
        // *** Equiv kludge: Shell.Exec("icacls", fileName + " /grant S-1-1-0:RX", here) |> ignore
        let fs = File.GetAccessControl(fileName)       // Mac OSX: System.NotSupportedException
        fs.AddAccessRule(new FileSystemAccessRule(     // 
                           new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                           FileSystemRights.ReadAndExecute,
                           AccessControlType.Allow))   // *** Seems pretty complex for such a simple
        File.SetAccessControl(fileName, fs)            //  task as just setting rx permissions!

  let shortClassName obj =                             // Just class name, no namespacey qualifiers
    let str = obj.ToString() in str.Substring(str.LastIndexOf('.') + 1)
  logfn "  Template %s (%s) generating %s (%s, %s EOLs%s)." 
    fromTemplate (shortClassName fromEncoding)         // Where we're coming from, 
    toFile       (shortClassName toEncoding)           //  where we're going,
    (if unixEOL then "Unix" else "DOS")                //  and in what condition we propose to
    (if readExecute then ", read/execute" else "")     //  arrive there

  fromTemplate                                         // Start with the input template filename
    |> ReadTemplate fromEncoding                       //  read it in lazily (enumeration of lines)
    |> SubstituteVariables substs                      //  substitute variables in each line
    |> MaybeAddReturn                                  //  fiddle with EOLs for target platform
    |> WriteTemplate toEncoding toFile                 //  write out in destination encoding
    |> MaybeSetReadExecute                             //  finally set permissions if asked

, , TemplateHelper, . , , , - . - , EOL , , .

:

Target "BuildLauncherScripts" (fun _ ->                // Scripts really only needed for mono
  let asciiEncoding = new ASCIIEncoding()              // Avoid UTF-8 BOM which breaks shebang lines
  let generateAScript (fromTempl : String) =           // Instantiate a script from template file
    let ext = fromTempl.Substring(fromTempl.LastIndexOf('.'))
    processTemplate projProps                          //   projProps to substitute template vars
      asciiEncoding (srcTem   @@ fromTempl)            //   copy from templates to build area
      asciiEncoding (buildApp @@ (projProps.Item("@product")) + ext)
      (ext = ".sh") true                               //   always executable, because it a script
  generateAScript "mono-launcher.sh"                   // Unix script to launch mono + application
  generateAScript "bat-launcher.bat"                   // Windows script to just launch application
)                                                      // 

  • TemplateHelper /, EOL /?
  • , ?
  • In particular, can the EOL code work on Windows or will it double upon return?
  • Also, is there a way to set read / execute permissions on a neutral platform? (My code above MaybeSetReadExecuteis a frighteningly mixed combination of crawling and emulating some .NET code that I found but hardly understand.)
+4
source share

No one has answered this question yet.


All Articles