Source file b0_cmd_scope.ml
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
open B0_std
open Result.Syntax
let is_vcs ~all find (_, dir) =
let* vcs = find ?dir:(Some dir) () in
match vcs with
| None -> Ok false
| Some vcs -> if all then Ok true else B00_vcs.is_dirty vcs
let get_scopes c root excludes k =
Log.if_error ~use:B0_driver.Exit.no_b0_file @@
let* b0_file = B0_driver.Conf.get_b0_file c in
Log.if_error' ~header:"" ~use:B0_driver.Exit.b0_file_error @@
let* s = Os.File.read b0_file in
let* src = B0_file.of_string ~file:b0_file s in
let* incs = match root with
| true -> Ok (B0_file.b0_includes src)
| false ->
let* e = B0_file.expand src in
Ok (B0_file.expanded_b0_includes e)
in
let inc_to_scope ((n, _), (p, _)) = n, Fpath.parent p in
let root = ("." , Fpath.parent b0_file) in
let scopes = root :: List.sort compare (List.map inc_to_scope incs) in
k (List.filter (fun (n, _) -> not (List.mem n excludes)) scopes)
let exec_when cond c root excludes keep_going cmd =
let err (_, dir) e =
Log.err (fun m -> m "@[%a: %s@]" Fpath.pp dir e);
Ok B00_cli.Exit.some_error
in
get_scopes c root excludes @@ function scopes ->
let rec loop = function
| [] -> Ok B00_cli.Exit.ok
| (n, p as s) :: ss ->
match cond s with
| Error e -> err s e
| Ok false -> loop ss
| Ok true ->
Log.app begin fun m ->
m "@[%a: %a@]"
Fmt.(code string) n (Fmt.tty [`Faint] Fpath.pp) p
end;
match Os.Cmd.run ~cwd:p cmd with
| Error e when not keep_going -> err s e
| Error _ | Ok () -> Log.app (fun m -> m ""); loop ss
in
loop scopes
let list root excludes format path c =
get_scopes c root excludes @@ function scopes ->
let pp_scope = match path with
| true -> fun ppf (_, dir) -> Fpath.pp_unquoted ppf dir
| false ->
match format with
| `Short -> fun ppf (n, _) -> Fmt.(code string) ppf n
| `Normal | `Long ->
fun ppf (n, dir) ->
Fmt.pf ppf "@[%a %a@]" Fmt.(code string) n Fpath.pp_unquoted dir
in
Log.app (fun m -> m "@[<v>%a@]" Fmt.(list pp_scope) scopes);
Ok B00_cli.Exit.ok
let exec root excludes keep_going tool tool_args c =
let cmd = tool :: tool_args in
exec_when (fun _ -> Ok true) c root excludes keep_going (Cmd.list cmd)
let git root excludes all keep_going full_cmd subcmd subcmd_args c =
let cmd = subcmd :: subcmd_args in
let cmd = if full_cmd then Cmd.list cmd else Cmd.(atom "git" %% list cmd) in
exec_when (is_vcs ~all B00_vcs.Git.find) c root excludes keep_going cmd
let hg root excludes all keep_going full_cmd subcmd subcmd_args c=
let cmd = subcmd :: subcmd_args in
let cmd = if full_cmd then Cmd.list cmd else Cmd.(atom "hg" %% list cmd) in
exec_when (is_vcs ~all B00_vcs.Hg.find) c root excludes keep_going cmd
open Cmdliner
let root =
let doc = "Only consider scopes included by the root B0 file. Those \
recursively included by these are excluded."
in
Arg.(value & flag & info ["root"] ~doc)
let excludes =
let doc = "Exclude scope $(docv) from the request. Repeatable." in
Arg.(value & opt_all string [] & info ["x"; "exclude"] ~doc ~docv:"SCOPE")
let keep_going =
let doc = "Do not stop if a tool invocation exits with non zero." in
Arg.(value & flag & info ["k"; "keep-going"] ~doc)
let full_cmd =
let doc = "Specify a full command rather than a subcommand of the VCS." in
Arg.(value & flag & info ["c"; "full-cmd"] ~doc)
let tool =
let doc = "Invoke tool $(docv)." in
Arg.(required & pos 0 (some string) None & info [] ~doc ~docv:"TOOL")
let all =
let doc = "Apply command to all VCS scopes, not only those that are dirty." in
Arg.(value & flag & info ["a"; "all"] ~doc)
let vcs_subcmd =
let doc = "Invoke VCS subcommand $(docv)." in
Arg.(required & pos 0 (some string) None & info [] ~doc ~docv:"SUBCMD")
let tool_args =
let doc = "Argument for the tool. Start with a $(b,--) \
token otherwise options get interpreted by $(mname)."
in
Arg.(value & pos_right 0 string [] & info [] ~doc ~docv:"ARG")
let list_term =
let path =
let doc = "Only print the scope paths." in
Arg.(value & flag & info ["path"] ~doc)
in
Term.(const list $ root $ excludes $ B0_b0.Cli.format $ path)
let vcs_syn =
"$(mname) $(b,scope) $(tname) [$(i,OPTION)]… $(b,--) $(i,SUBCMD) [$(i,ARG)]…"
let exec =
let doc = "Execute a tool in scope directories" in
let synopsis = `P "$(mname) $(b,scope) $(tname) [$(i,OPTION)]… $(b,--) \
$(i,TOOL) [$(i,ARG)]…"
in
let descr = `P "$(tname) executes $(i,TOOL) with given arguments in the \
directory of each of the scopes. The process is stopped \
if $(i,TOOL) returns with a non zero exit code, use the \
option $(b,--keep-going) to prevent that."
in
B0_b0.Cli.subcmd_with_driver_conf "exec" ~doc ~synopsis ~descr
Term.(const exec $ root $ excludes $ keep_going $ tool $ tool_args)
let hg =
let doc = "Execute $(b,hg) in dirty Mercurial managed scopes" in
let synopsis = `P vcs_syn in
let descr = `P "$(tname) works exactly like $(b,b0 scope git) but with the \
Mercurial VCS, see $(mname) $(b,scope git --help) for
more information"
in
B0_b0.Cli.subcmd_with_driver_conf "hg" ~doc ~synopsis ~descr
Term.(const hg $ root $ excludes $ all $ keep_going $ full_cmd $
vcs_subcmd $ tool_args)
let git =
let doc = "Execute $(b,git) in dirty Git managed scopes" in
let synopsis = `P vcs_syn in
let descr = `Blocks [
`P "$(tname) executes the Git subcommand $(i,SUBCMD) \
with given arguments in the directory of each of the scopes
which are found to be managed by Git and dirty;
or all of them if $(b,--all) is specified.";
`P "If $(b,--full-cmd) is specified the positional arguments specify a
full command like $(b,scope exec) does, not a VCS subcommand.";
`P "The process is stopped if an execution returns with a non zero exit
code, use the option $(b,--keep-going) to prevent that." ]
in
B0_b0.Cli.subcmd_with_driver_conf "git" ~doc ~synopsis ~descr
Term.(const git $ root $ excludes $ all $ keep_going $ full_cmd $
vcs_subcmd $ tool_args)
let list =
let doc = "List scopes (default command)" in
let descr = `P "$(tname) lists scope names and their location. \
If $(b,--path) is specified only paths are listed."
in
let envs = B0_b0.Cli.pager_envs in
B0_b0.Cli.subcmd_with_driver_conf "list" ~doc ~descr ~envs list_term
let subs = [exec; hg; git; list;]
let cmd =
let doc = "Operate on B0 scopes" in
let descr =
`Blocks [
`P "$(tname) operates on scopes. The default command is $(tname) \
$(b,list).";
`P "$(tname) can fold over scope directories and bulk operate \
their VCSs (if applicable) when repositories are dirty. \
Typical usage:";
`P "$(b,> b0)"; `Noblank;
`P "Error: ..."; `Noblank;
`P "$(b,> ... # Fix errors)"; `Noblank;
`P "$(b,> b0)"; `Noblank;
`P "$(b,> b0 scope git -- status)"; `Noblank;
`P "$(b,> b0 scope git -- add -p)"; `Noblank;
`P "$(b,> b0 scope git -- commit -m 'Cope with changes!')"; `Noblank;
`P "$(b,> b0 scope git --all -- push)";
`P "To invoke arbitrary tools in scopes use $(b,b0 scope exec). Options
$(b,--root) and $(b,-x) allow to prune the list of scopes.";
]
in
let default = list_term in
B0_b0.Cli.cmd_group_with_driver_conf "scope" ~doc ~descr ~default subs