mirror of
https://github.com/chylex/TweetDuck.git
synced 2024-11-23 08:42:47 +01:00
250 lines
9.4 KiB
Plaintext
250 lines
9.4 KiB
Plaintext
open System
|
|
open System.Collections.Generic
|
|
open System.Diagnostics
|
|
open System.IO
|
|
open System.Text.RegularExpressions
|
|
open System.Threading.Tasks
|
|
|
|
// "$(DevEnvDir)CommonExtensions\Microsoft\FSharp\fsi.exe" "$(ProjectDir)Resources\PostBuild.fsx" --exec --nologo -- "$(TargetDir)\" "$(ProjectDir)\" "$(ConfigurationName)"
|
|
// "$(ProjectDir)bld\post_build.exe" "$(TargetDir)\" "$(ProjectDir)\" "$(ConfigurationName)"
|
|
|
|
exception ArgumentUsage of string
|
|
|
|
#if !INTERACTIVE
|
|
[<EntryPoint>]
|
|
#endif
|
|
let main (argv: string[]) =
|
|
try
|
|
if argv.Length < 2 then
|
|
#if INTERACTIVE
|
|
raise (ArgumentUsage "fsi.exe PostBuild.fsx --exec --nologo -- <TargetDir> <ProjectDir> [ConfigurationName] [VersionTag]")
|
|
#else
|
|
raise (ArgumentUsage "PostBuild.exe <TargetDir> <ProjectDir> [ConfigurationName] [VersionTag]")
|
|
#endif
|
|
|
|
let _time name func =
|
|
let sw = Stopwatch.StartNew()
|
|
func()
|
|
sw.Stop()
|
|
printfn "[%s took %i ms]" name (int (Math.Round(sw.Elapsed.TotalMilliseconds)))
|
|
|
|
let (+/) path1 path2 =
|
|
Path.Combine(path1, path2)
|
|
|
|
let sw = Stopwatch.StartNew()
|
|
|
|
// Setup
|
|
|
|
let targetDir = argv.[0]
|
|
let projectDir = argv.[1]
|
|
|
|
let configuration = if argv.Length >= 3 then argv.[2]
|
|
else "Release"
|
|
|
|
let version = if argv.Length >= 4 then argv.[3]
|
|
else ((targetDir +/ "TweetDuck.exe") |> FileVersionInfo.GetVersionInfo).FileVersion
|
|
|
|
printfn "--------------------------"
|
|
printfn "TweetDuck version %s" version
|
|
printfn "--------------------------"
|
|
|
|
let localesDir = targetDir +/ "locales"
|
|
let scriptsDir = targetDir +/ "scripts"
|
|
let pluginsDir = targetDir +/ "plugins"
|
|
let importsDir = scriptsDir +/ "imports"
|
|
|
|
// Functions (Strings)
|
|
|
|
let filterNotEmpty =
|
|
Seq.filter (not << String.IsNullOrEmpty)
|
|
|
|
let replaceRegex (pattern: string) (replacement: string) input =
|
|
Regex.Replace(input, pattern, replacement)
|
|
|
|
let collapseLines separator (sequence: string seq) =
|
|
String.Join(separator, sequence)
|
|
|
|
let trimStart (line: string) =
|
|
line.TrimStart()
|
|
|
|
// Functions (File Management)
|
|
let copyFile source target =
|
|
File.Copy(source, target, true)
|
|
|
|
let createDirectory path =
|
|
Directory.CreateDirectory(path) |> ignore
|
|
|
|
let rec copyDirectoryContentsFiltered source target (filter: string -> bool) =
|
|
if not (Directory.Exists(target)) then
|
|
Directory.CreateDirectory(target) |> ignore
|
|
|
|
let src = DirectoryInfo(source)
|
|
|
|
for file in src.EnumerateFiles() do
|
|
if filter file.Name then
|
|
file.CopyTo(target +/ file.Name) |> ignore
|
|
|
|
for dir in src.EnumerateDirectories() do
|
|
if filter dir.Name then
|
|
copyDirectoryContentsFiltered dir.FullName (target +/ dir.Name) filter
|
|
|
|
let copyDirectoryContents source target =
|
|
copyDirectoryContentsFiltered source target (fun _ -> true)
|
|
|
|
// Functions (File Processing)
|
|
|
|
let byPattern path pattern =
|
|
Directory.EnumerateFiles(path, pattern, SearchOption.AllDirectories)
|
|
|
|
let exceptEndingWith name =
|
|
Seq.filter (fun (file: string) -> not (file.EndsWith(name)))
|
|
|
|
let iterateFiles (files: string seq) (func: string -> unit) =
|
|
Parallel.ForEach(files, func) |> ignore
|
|
|
|
let readFile file = seq {
|
|
use stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0x1000, FileOptions.SequentialScan)
|
|
use reader = new StreamReader(stream)
|
|
let mutable cont = true
|
|
|
|
while cont do
|
|
let line = reader.ReadLine()
|
|
|
|
if line = null then
|
|
cont <- false
|
|
else
|
|
yield line
|
|
}
|
|
|
|
let writeFile (fullPath: string) (lines: string seq) =
|
|
let relativePath = fullPath.[(targetDir.Length)..]
|
|
let includeVersion = relativePath.StartsWith(@"scripts\") && not (relativePath.StartsWith(@"scripts\imports\"))
|
|
let finalLines = if includeVersion then seq { yield "#" + version; yield! lines } else lines
|
|
|
|
File.WriteAllLines(fullPath, finalLines |> filterNotEmpty |> Seq.toArray)
|
|
printfn "Processed %s" relativePath
|
|
|
|
let processFiles (files: string seq) (extProcessors: IDictionary<string, (string seq -> string seq)>) =
|
|
let rec processFileContents file =
|
|
readFile file
|
|
|> extProcessors.[Path.GetExtension(file)]
|
|
|> Seq.map (fun line ->
|
|
Regex.Replace(line, @"#import ""(.*?)""", (fun matchInfo ->
|
|
processFileContents(importsDir +/ matchInfo.Groups.[1].Value.Trim())
|
|
|> collapseLines (Environment.NewLine)
|
|
|> (fun contents -> contents.TrimEnd())
|
|
))
|
|
)
|
|
|
|
iterateFiles files (fun file ->
|
|
processFileContents file
|
|
|> (writeFile file)
|
|
)
|
|
|
|
// Build
|
|
|
|
copyFile (projectDir +/ "bld/Resources/LICENSES.txt") (targetDir +/ "LICENSES.txt")
|
|
|
|
copyDirectoryContents (projectDir +/ "Resources/Scripts") scriptsDir
|
|
|
|
createDirectory (pluginsDir +/ "official")
|
|
createDirectory (pluginsDir +/ "user")
|
|
|
|
copyDirectoryContentsFiltered
|
|
(projectDir +/ "Resources/Plugins")
|
|
(pluginsDir +/ "official")
|
|
(fun name -> name <> ".debug" && name <> "emoji-instructions.txt")
|
|
|
|
if configuration = "Debug" then
|
|
copyDirectoryContents
|
|
(projectDir +/ "Resources/Plugins/.debug")
|
|
(pluginsDir +/ "user/.debug")
|
|
|
|
if Directory.Exists(localesDir) || configuration = "Release" then
|
|
Directory.EnumerateFiles(localesDir, "*.pak")
|
|
|> exceptEndingWith @"\en-US.pak"
|
|
|> Seq.iter (File.Delete)
|
|
|
|
// Validation
|
|
|
|
if File.ReadAllText(pluginsDir +/ "official/emoji-keyboard/emoji-ordering.txt").IndexOf('\r') <> -1 then
|
|
raise (FormatException("emoji-ordering.txt must not have any carriage return characters"))
|
|
else
|
|
printfn "Verified emoji-ordering.txt"
|
|
|
|
// Processing
|
|
|
|
let fileProcessors =
|
|
dict [
|
|
".js", (fun (lines: string seq) ->
|
|
lines
|
|
|> Seq.map (fun line ->
|
|
line
|
|
|> trimStart
|
|
|> replaceRegex @"^(.*?)((?<=^|[;{}()])\s?//(?:\s.*|$))?$" "$1"
|
|
|> replaceRegex @"(?<!\w)(return|throw)(\s.*?)? if (.*?);" "if ($3)$1$2;"
|
|
)
|
|
);
|
|
|
|
".css", (fun (lines: string seq) ->
|
|
lines
|
|
|> Seq.map (fun line ->
|
|
line
|
|
|> replaceRegex @"\s*/\*.*?\*/" ""
|
|
|> replaceRegex @"^(\S.*) {$" "$1{"
|
|
|> replaceRegex @"^\s+(.+?):\s*(.+?)(?:\s*(!important))?;$" "$1:$2$3;"
|
|
)
|
|
|> filterNotEmpty
|
|
|> collapseLines " "
|
|
|> replaceRegex @"([{};])\s" "$1"
|
|
|> replaceRegex @";}" "}"
|
|
|> Seq.singleton
|
|
);
|
|
|
|
".html", (fun (lines: string seq) ->
|
|
lines
|
|
|> Seq.map trimStart
|
|
);
|
|
|
|
".meta", (fun (lines: string seq) ->
|
|
lines
|
|
|> Seq.map (fun line -> line.Replace("{version}", version))
|
|
);
|
|
]
|
|
|
|
processFiles ((byPattern targetDir "*.js") |> exceptEndingWith @"\configuration.default.js") fileProcessors
|
|
processFiles (byPattern targetDir "*.css") fileProcessors
|
|
processFiles (byPattern targetDir "*.html") fileProcessors
|
|
processFiles (byPattern pluginsDir "*.meta") fileProcessors
|
|
|
|
// Cleanup
|
|
|
|
Directory.Delete(importsDir, true)
|
|
|
|
// Finished
|
|
|
|
sw.Stop()
|
|
printfn "------------------"
|
|
printfn "Finished in %i ms" (int (Math.Round(sw.Elapsed.TotalMilliseconds)))
|
|
printfn "------------------"
|
|
0
|
|
|
|
with
|
|
| ArgumentUsage message ->
|
|
printfn ""
|
|
printfn "Build script usage:"
|
|
printfn "%s" message
|
|
printfn ""
|
|
1
|
|
| ex ->
|
|
printfn ""
|
|
printfn "Encountered an error while running PostBuild:"
|
|
printfn "%A" ex
|
|
printfn ""
|
|
1
|
|
|
|
#if INTERACTIVE
|
|
printfn "Running PostBuild in interpreter..."
|
|
main (fsi.CommandLineArgs |> Array.skip (1 + (fsi.CommandLineArgs |> Array.findIndex (fun arg -> arg = "--"))))
|
|
#endif
|