1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
| // -------------------------------------------------------------------------------
// Initial rename of library and project script
// -------------------------------------------------------------------------------
// Bind operator
let (>>=) m f = Option.bind f m
// Args function that parses command line arguments
let getArg argv key =
let arg = Array.tryFind(fun (a:string) -> a.StartsWith(key)) argv
match arg with
| Some x -> x.Replace(key, "") |> Some
| None -> None
// Thread-safe console logger
let ts () = System.DateTime.Now.ToString("o") // ISO-8601
let cw (s:string) = System.Console.WriteLine(s) // Threadsafe console writer
let cew (s:string) = System.Console.Error.WriteLine(s) // Threadsafe console error writer
type LogLevel = Info | Warning | Error
let log level x y =
let msg = sprintf "%s - %A: %A (%A)" (ts()) level x y
match level with
| LogLevel.Error -> cew msg
| LogLevel.Info | LogLevel.Warning -> cw msg
// Generic process executer (needed for "git mv source target")
let executeProcess exe args dir =
try
let psi = new System.Diagnostics.ProcessStartInfo(exe,args)
psi.CreateNoWindow <- true
psi.UseShellExecute <- false
psi.RedirectStandardOutput <- true
psi.RedirectStandardError <- true
psi.WorkingDirectory <- dir
let p = System.Diagnostics.Process.Start(psi)
let o = new System.Text.StringBuilder()
let e = new System.Text.StringBuilder()
p.OutputDataReceived.Add(fun x -> o.AppendLine(x.Data) |> ignore)
p.ErrorDataReceived.Add(fun x -> e.AppendLine(x.Data) |> ignore)
p.BeginErrorReadLine()
p.BeginOutputReadLine()
p.WaitForExit()
(p.ExitCode, o.ToString(), e.ToString()) |> Some
with ex -> log LogLevel.Error (exe,args,dir) ex; None
// Scaffold & Template
let scaffold = "FSharp.ProjectScaffold"
let template = "FSharp.ProjectTemplate"
// The name of the library (will replace "FSharp.ProjectScaffold")
let lib =
((fsi.CommandLineArgs,"lib=") ||> getArg, "FSharp.Foo")
||> defaultArg
// The name of the project (will replace "FSharp.ProjectTemplate")
let proj =
((fsi.CommandLineArgs,"proj=") ||> getArg, "FSharp.Bar")
||> defaultArg
// Folder & file helper functions
let root = __SOURCE_DIRECTORY__
let recursively = System.IO.SearchOption.AllDirectories
let pattern filter = "*" + filter + "*"
let pattern' filter = "*" + filter
let dirs path filter =
System.IO.Directory.EnumerateDirectories(path,filter,recursively)
let files path filter =
System.IO.Directory.EnumerateFiles(path,filter,recursively)
let rev (s:string) =
s |> Seq.toArray |> Array.fold(fun a x -> (x |> string) + a) ""
let replaceFirst input from to' =
let r = new System.Text.RegularExpressions.Regex(from)
r.Replace(input = input,replacement = to', count = 1)
let isGit =
let exe = "git"
let args = sprintf "status"
let git = (exe,args,root) |||> executeProcess
git |> function | Some (x,y,z) -> x = 0 | None -> false
let renameGit path path' =
let exe = "git"
let args = sprintf "mv \"%s\" \"%s\"" path path'
(exe,args,root) |||> executeProcess, path, path'
let renameDirs path path' =
System.IO.Directory.Move(path,path') |> ignore
(0,"","") |> Some,path,path'
let renameFiles path path' =
System.IO.File.Move(path,path') |> ignore
(0,"","") |> Some,path,path'
let rename' path path' =
match isGit with
| true -> (path,path') ||> renameGit
| false ->
match System.IO.File.GetAttributes(path) with
| System.IO.FileAttributes.Directory -> (path,path') ||> renameDirs
| _ -> (path,path') ||> renameFiles
let rename (path:string) from to' =
let from' = from |> rev
let to'' = to' |> rev
let path' = (path |> rev, from', to'') |||> replaceFirst |> rev
(path,path') ||> rename'
let rollback xs = xs |> List.iter(fun (x,y) -> (y,x) ||> rename' |> ignore)
// File content helper functions
let utf8 = System.Text.UTF8Encoding.UTF8
let readLines path = System.IO.File.ReadLines(path,utf8)
let writeLines path (contents:string seq) =
System.IO.File.WriteAllLines(path,contents,utf8)
let copy from to' =
System.IO.File.Copy(from,to',true)
let delete path = System.IO.File.Delete(path)
let extensions = [ ".sln"; ".fs"; ".fsx"; ".fsproj"; ".nuspec"; ".md" ]
// Rename files or directories
let renameIO from to' fn atomic' =
try
(root,from |> pattern) ||> fn
|> Seq.map(fun x -> (x,from,to') |||> rename)
|> Seq.fold(fun (i,acc) (x,y,z) ->
let i' =
match x with
| Some (a,b,c) -> a
| None -> 1
(i+i',(y,z)::acc)) (0,[])
|> fun (x,y) ->
match x with
| 0 -> (y,atomic') ||> List.append |> Some
| _ -> atomic' |> rollback; None
with ex -> log LogLevel.Error (atomic',from,to') ex; None
// Update files content
let updateContent exts atomic' =
try
exts
|> Seq.map(fun x -> (root,x |> pattern') ||> files)
|> Seq.fold(fun a x -> (x,a) ||> Seq.append) Seq.empty
|> Seq.filter(fun x -> not (x.Contains "rename.fsx"))
|> Seq.fold(fun a x ->
let x' = x + "_"
x |> readLines
|> Seq.map(fun y -> y.Replace(scaffold,lib)
.Replace(template,proj))
|> writeLines x'
(x,x')::a) []
|> Seq.iter(fun (x,y) -> (y,x) ||> copy; y |> delete)
|> Some
with ex ->
let git =
match isGit with
| false -> (0,"","") |> Some // Not really rollback but ...
| true ->
let exe = "git"
let args = sprintf "checkout -- *"
(exe,args,root) |||> executeProcess
atomic' |> rollback
log LogLevel.Error (exts,git) ex; None
// Rename with atomicity "git mv file2 file1"
[] |> Some >>= (renameIO scaffold lib dirs)
>>= (renameIO template proj dirs)
>>= (renameIO scaffold lib files)
>>= (renameIO template proj files)
// Update content
>>= (updateContent extensions)
|