Source file cmd.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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
open! Core
open! Import
(** Services that we don't yet support for some reason. *)
let unsupported_services : String.Set.t =
String.Set.of_list
[ "apigateway"
; "apigatewayv2"
; "appconfig"
; "appconfigdata"
; "appsync"
; "cloudhsm"
; "codeguruprofiler"
; "dataexchange"
; "health"
; "iottwinmaker"
; "lex-runtime"
; "lexv2-runtime"
; "lookoutvision"
; "mediaconnect"
; "medialive"
; "mq"
; "pinpoint"
; "s3control"
; "sagemaker-runtime"
; "workmailmessageflow"
]
;;
let get_all_services ~(botocore_data : string) : string list =
Sys_unix.ls_dir botocore_data
|> List.filter ~f:(fun ent -> Sys_unix.is_directory_exn (botocore_data ^/ ent))
|> List.sort ~compare:String.compare
;;
let latest_date ~botocore_data ~service =
let dir = botocore_data ^/ service in
match Sys_unix.is_directory_exn dir with
| false -> Error `Unknown_directory
| true ->
let date =
dir
|> Sys_unix.ls_dir
|> List.sort ~compare:String.compare
|> List.rev
|> List.hd_exn
in
Ok date
;;
module Param = struct
let service_name ?(flag = "service") () =
let name = sprintf "--%s" flag in
Command.Param.(
flag
name
(required string)
~doc:
"STRING Name of a service. Should match a subdirectory name under \
botocore/data.")
;;
let service_date =
Command.Param.(
flag
"--service-date"
(required string)
~doc:
"YYYY-MM Service date. Should match a subdirectory name under the respective \
service in botocore/data.")
;;
let botocore_data =
Command.Param.(
flag
"--botocore-data"
(required string)
~doc:"DIR Path to the botocore/data directory.")
;;
let service_names =
Command.Param.(
flag
"--services"
(optional string)
~doc:
"NAMES Comma separated list of services. Default: all services (except those \
not currently supported).")
;;
let botocore_data_and_service_names : (string * string list) Command.Param.t =
let make botocore_data services =
let services =
match services with
| Some x -> x |> String.split ~on:',' |> List.map ~f:String.strip
| None ->
let all = get_all_services ~botocore_data |> String.Set.of_list in
let unsupported = unsupported_services in
Set.diff all unsupported |> Set.to_list
in
botocore_data, services
in
let open Command.Param.Applicative_infix in
Command.Param.return make <*> botocore_data <*> service_names
;;
let endpoints_json_file =
Command.Param.(
flag
"--endpoints"
(required string)
~doc:"PATH Path to the botocore/data/endpoints.json file.")
;;
let service_json_file =
Command.Param.(
flag
"--service"
(required string)
~doc:"PATH Path to a service*.json file within botocore.")
;;
let outdir = Command.Param.(flag "-o" (required string) ~doc:"DIR Output directory.")
let impl =
Command.Param.(
flag
"--impl"
(required string)
~doc:"PATH Path where the .ml file should be written.")
;;
let intf =
Command.Param.(
flag
"--intf"
(required string)
~doc:"PATH Path where the .mli file should be written.")
;;
let submodules =
Command.Param.(
flag
"--sub"
(listed string)
~doc:"Names of sub-modules (for breaking up huge module compilation)")
;;
let io_subsystem =
Command.Param.(flag "--io-subsystem" (required string) ~doc:"SUBSYS async or lwt")
|> Command.Param.map ~f:(function
| "async" -> `Async
| "lwt" -> `Lwt
| fmt -> failwithf "Unknown io-subsystem: %s" fmt ())
;;
let base_module =
Command.Param.(
flag
"--base-module"
(required string)
~doc:"In which module Endpoints & Values are searched")
;;
end
let botocore_endpoints : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate Botocore_endpoints module"
[%map_open
let file = Param.endpoints_json_file in
fun () ->
let endpoints = file |> In_channel.read_all |> Botocore_endpoints.of_json in
let structure =
[ Botocore_endpoints.make_lookup_uri endpoints
; Botocore_endpoints.make_lookup_credential_scope endpoints
]
in
print_endline (Util.structure_to_string structure)]
;;
module Service = struct
let dune : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate a service's dune file"
[%map_open
let service = Param.service_name ()
and date = Param.service_date in
fun () -> Dune.make ~date ~service |> print_endline]
;;
let endpoints : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate a service's Endpoints module"
[%map_open
let service = Param.service_json_file
and impl = Param.impl in
fun () ->
let data =
service
|> In_channel.read_all
|> Botocore_service.of_json
|> Service_endpoints.make
|> Util.structure_to_string
in
Out_channel.write_all impl ~data]
;;
let values : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate a service's Values module"
[%map_open
let awsm_service_id = Param.service_name ~flag:"service-id" ()
and service = Param.service_json_file
and impl = Param.impl
and submodules = Param.submodules in
fun () ->
let service = service |> In_channel.read_all |> Botocore_service.of_json in
let main_module, submodules =
Values.make ~awsm_service_id ~submodules service
in
Out_channel.write_all impl ~data:(main_module |> Util.structure_to_string);
submodules
|> List.iter ~f:(fun (filename, struct_) ->
Out_channel.write_all filename ~data:(struct_ |> Util.structure_to_string))]
;;
let main : Command.t =
Command.group
~summary:"generate a service's package"
~readme:(fun () ->
"The dune subcommand generates a dune file that contains rules to generate all\n\
other files. Thus, you either need to call only the dune subcommand, or all the\n\
other subcommands, but not both.")
[ "dune", dune; "endpoints", endpoints; "values", values ]
;;
end
module Service_io = struct
let dune : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate a service's dune file"
[%map_open
let service = Param.service_name ()
and date = Param.service_date
and io_subsystem = Param.io_subsystem in
fun () -> Dune.make_io io_subsystem ~date ~service |> print_endline]
;;
let values : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"gnerate a service's Values module"
[%map_open
let service = Param.service_name () in
fun () ->
let service =
service
|> String.map ~f:(function
| '-' -> '_'
| c -> c)
in
printf
{|(* do not edit! generated module *)
include Awsm_%s.Values|}
service]
;;
let io : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate a service's IO module"
[%map_open
let service = Param.service_json_file
and impl = Param.impl
and intf = Param.intf
and base_module = Param.base_module
and io_subsystem = Param.io_subsystem in
fun () ->
let service = service |> In_channel.read_all |> Botocore_service.of_json in
let endpoints =
service.operations |> List.map ~f:(Endpoint.of_botodata ~service)
in
let () =
Io.eval_structure ~base_module ~io_subsystem endpoints
|> Util.structure_to_string
|> fun data -> Out_channel.write_all impl ~data
in
let () =
Io.eval_signature
~protocol:service.metadata.protocol
~base_module
~io_subsystem
endpoints
|> Util.signature_to_string
|> fun data -> Out_channel.write_all intf ~data
in
()]
;;
let cli : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate a service's Cli module"
~readme:(fun () ->
"The generated module depends on Core and Async. Also, this is only provides a\n\
value of type Command.t. You still need to define an executable by calling\n\
Command.run. We provide another command for generating such an executable.")
[%map_open
let service = Param.service_json_file
and impl = Param.impl
and submodules = Param.submodules in
fun () ->
let service = service |> In_channel.read_all |> Botocore_service.of_json in
let main_module, submodules = Cli.make ~submodules service in
Out_channel.write_all impl ~data:(main_module |> Util.structure_to_string);
submodules
|> List.iter ~f:(fun (filename, struct_) ->
Out_channel.write_all filename ~data:(struct_ |> Util.structure_to_string))]
;;
let main : Command.t =
Command.group
~summary:"generate a service's IO package"
~readme:(fun () ->
"The dune subcommand generates a dune file that contains rules to generate all\n\
other files. Thus, you either need to call only the dune subcommand, or all the\n\
other subcommands, but not both.\n\n\
We currently support the creation of packages for async and lwt. If async is\n\
selected, we also generate a CLI.")
[ "dune", dune; "cli", cli; "io", io; "values", values ]
;;
end
module Cli = struct
let dune : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate a dune file"
[%map_open
let service = Param.service_name () in
fun () -> Dune.make_cli_async ~service |> print_endline]
;;
let script : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate a script"
[%map_open
let service = Param.service_name () in
fun () ->
let service =
service
|> String.map ~f:(function
| '-' -> '_'
| c -> c)
in
printf
{|(* do not edit! generated module *)
let () = Command_unix.run Awsm_%s_async.Cli.main
|}
service]
;;
let main : Command.t =
Command.group
~summary:"generate a package providing a service's CLI"
~readme:(fun () ->
"The dune subcommand generates a dune file that contains rules to generate all\n\
other files. Thus, you either need to call only the dune subcommand, or all the\n\
other subcommands, but not both.")
[ "dune", dune; "script", script ]
;;
end
module Services = struct
let main : Command.t =
let open Command.Let_syntax in
Command.basic
~summary:"generate multiple services' packages"
[%map_open
let botocore_data, services = Param.botocore_data_and_service_names
and outdir = Param.outdir in
fun () ->
let temp_file = Filename_unix.temp_file "dune" "" in
let print_dune_file ~outdir ~data =
Out_channel.write_all temp_file ~data;
let prog = "dune" in
let args = [ "format-dune-file"; temp_file ] in
match Process.run ~prog ~args with
| Error exn ->
failwithf
"%s %s\n%s"
prog
(args |> String.concat ~sep:" ")
(Exn.to_string exn)
()
| Ok { stdout; exit_status; stderr = _ } -> (
match exit_status with
| Error e ->
failwithf
"%s"
(e |> Core_unix.Exit_or_signal.sexp_of_error |> Sexp.to_string)
()
| Ok () ->
Util.mkdir_exn outdir;
Out_channel.write_all (outdir ^/ "dune") ~data:stdout)
in
let () =
services
|> List.iter ~f:(fun service ->
match latest_date ~botocore_data ~service with
| Error `Unknown_directory ->
failwithf "Unknown directory: %s/%s" botocore_data service ()
| Ok date ->
print_dune_file
~outdir:(outdir ^/ service)
~data:(Dune.make ~date ~service);
print_dune_file
~outdir:(outdir ^/ service ^ "-async")
~data:(Dune.make_io `Async ~date ~service);
print_dune_file
~outdir:(outdir ^/ service ^ "-cli-async")
~data:(Dune.make_cli_async ~service))
in
Sys_unix.remove temp_file]
;;
end
let main =
Command.group
~summary:"Code generation from boto data"
[ "botocore-endpoints", botocore_endpoints
; "service", Service.main
; "service-io", Service_io.main
; "services", Services.main
; "cli", Cli.main
]
;;