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
|
open Import
open Dune_init
(** {1 Helper functions} *)
(** {2 Cmdliner Argument Converters} *)
let atom_parser s =
match Dune_lang.Atom.parse s with
| Some s -> Ok s
| None -> Error (`Msg "expected a valid dune atom")
;;
let atom_printer ppf a = Format.pp_print_string ppf (Dune_lang.Atom.to_string a)
let component_name_parser s =
(* TODO refactor to use Lib_name.Local.conv *)
let err_msg () =
User_error.make
[ Pp.textf "invalid component name `%s'" s; Lib_name.Local.valid_format_doc ]
|> User_message.to_string
|> fun m -> `Msg m
in
let open Result.O in
let* atom = atom_parser s in
let* _ =
match Lib_name.Local.of_string_opt s with
| None -> Error (err_msg ())
| Some s -> Ok s
in
Ok atom
;;
let project_name_parser s =
(* TODO refactor Dune_project_name to be Stringlike *)
match Dune_project_name.named Loc.none s with
| v -> Ok v
| exception User_error.E _ ->
User_error.make
[ Pp.textf "invalid project name `%s'" s
; Pp.text
"Project names must start with a letter and be composed only of letters, \
numbers, '-' or '_'"
]
|> User_message.to_string
|> fun m -> Error (`Msg m)
;;
let project_name_printer ppf p =
Format.pp_print_string ppf (Dune_project_name.to_string_hum p)
;;
let atom_conv = Arg.conv (atom_parser, atom_printer)
let component_name_conv = Arg.conv (component_name_parser, atom_printer)
let project_name_conv = Arg.conv (project_name_parser, project_name_printer)
(** {2 Status reporting} *)
let print_completion kind name =
let open Pp.O in
Console.print_user_message
(User_message.make
[ Pp.tag User_message.Style.Ok (Pp.verbatim "Success")
++ Pp.textf ": initialized %s component named " kind
++ Pp.tag User_message.Style.Kwd (Pp.verbatim (Dune_lang.Atom.to_string name))
])
;;
(** {1 CLI} *)
let path =
let docv = "PATH" in
Arg.(value & pos 1 (some string) None & info [] ~docv)
;;
let context_cwd : Init_context.t Term.t =
let+ builder = Common.Builder.term
and+ path = path in
let builder = Common.Builder.set_default_root_is_cwd builder true in
let common, config = Common.init builder in
let project_defaults = config.project_defaults in
Scheduler.go_with_rpc_server ~common ~config (fun () ->
Memo.run (Init_context.make path project_defaults))
;;
module Public_name = struct
type t =
| Use_name
| Public_name of Public_name.t
let public_name_to_string = function
| Use_name -> "<default>"
| Public_name p -> Public_name.to_string p
;;
let public_name default_name = function
| None -> None
| Some Use_name -> Some (Public_name.of_name_exn default_name)
| Some (Public_name n) -> Some n
;;
let conv =
let parser s =
if String.is_empty s
then Ok Use_name
else (
match Public_name.of_string_user_error (Loc.none, s) with
| Ok n -> Ok (Public_name n)
| Error e -> Error (`Msg (User_message.to_string e)))
in
let printer ppf public_name =
Format.pp_print_string ppf (public_name_to_string public_name)
in
Arg.conv (parser, printer)
;;
end
let libraries =
let docv = "LIBRARIES" in
let doc = "A comma separated list of libraries on which the component depends" in
Arg.(value & opt (list atom_conv) [] & info [ "libs" ] ~docv ~doc)
;;
let pps =
let docv = "PREPROCESSORS" in
let doc = "A comma separated list of ppx preprocessors used by the component" in
Arg.(value & opt (list atom_conv) [] & info [ "ppx" ] ~docv ~doc)
;;
let public : Public_name.t option Term.t =
let docv = "PUBLIC_NAME" in
let doc =
"If called with an argument, make the component public under the given PUBLIC_NAME. \
If supplied without an argument, use NAME."
in
Arg.(
value
& opt ~vopt:(Some Public_name.Use_name) (some Public_name.conv) None
& info [ "public" ] ~docv ~doc)
;;
let common : Component.Options.Common.t Term.t =
let+ name =
let docv = "NAME" in
Arg.(required & pos 0 (some component_name_conv) None & info [] ~docv)
and+ public = public
and+ libraries = libraries
and+ pps = pps in
let public = Public_name.public_name name public in
{ Component.Options.Common.name; public; libraries; pps }
;;
let project_common : Component.Options.Common.t Term.t =
let+ project_name =
let docv = "NAME" in
Arg.(required & pos 0 (some project_name_conv) None & info [] ~docv)
and+ libraries = libraries
and+ pps = pps in
let public = Dune_project_name.to_string_hum project_name in
let name =
String.map
~f:(function
| '-' -> '_'
| c -> c)
public
|> Dune_lang.Atom.of_string
in
let public =
Some (Dune_lang.Atom.of_string public |> Dune_init.Public_name.of_name_exn)
in
{ Component.Options.Common.name; public; libraries; pps }
;;
let inline_tests : bool Term.t =
let docv = "USE_INLINE_TESTS" in
let doc =
"Whether to use inline tests. Only applicable for $(b,library) and $(b,project) \
components."
in
Arg.(value & flag & info [ "inline-tests" ] ~docv ~doc)
;;
let opt_default ~default term = Term.(const (Option.value ~default) $ term)
let executable =
let doc = "A binary executable." in
let man = [] in
let kind = "executable" in
Cmd.v (Cmd.info kind ~doc ~man)
@@ let+ context = context_cwd
and+ common = common in
Component.init (Executable { context; common; options = () });
print_completion kind common.name
;;
let library =
let doc = "An OCaml library." in
let man = [] in
let kind = "library" in
Cmd.v (Cmd.info kind ~doc ~man)
@@ let+ context = context_cwd
and+ common = common
and+ inline_tests = inline_tests in
Component.init (Library { context; common; options = { inline_tests } });
print_completion kind common.name
;;
let test =
let doc =
"A test harness. (For inline tests, use the $(b,--inline-tests) flag along with the \
other component kinds.)"
in
let man = [] in
let kind = "test" in
Cmd.v (Cmd.info kind ~doc ~man)
@@ let+ context = context_cwd
and+ common = common in
Component.init (Test { context; common; options = () });
print_completion kind common.name
;;
let project =
let module Builder = Common.Builder in
let doc =
"A project is a predefined composition of components arranged in a standard \
directory structure. The kind of project initialized is determined by the value of \
the $(b,--kind) flag and defaults to an executable project, composed of a library, \
an executable, and a test component."
in
let man = [] in
Cmd.v (Cmd.info "project" ~doc ~man)
@@ let+ common_builder = Builder.term
and+ path = path
and+ common = project_common
and+ inline_tests = inline_tests
and+ template =
let docv = "PROJECT_KIND" in
let doc =
"The kind of project to initialize. Valid options are $(b,e[xecutable]) or \
$(b,l[ibrary]). Defaults to $(b,executable). Only applicable for $(b,project) \
components."
in
opt_default
~default:Component.Options.Project.Template.Exec
Arg.(
value
& opt (some (enum Component.Options.Project.Template.commands)) None
& info [ "kind" ] ~docv ~doc)
and+ pkg =
let docv = "PACKAGE_MANAGER" in
let doc =
"Which package manager to use. Valid options are $(b,o[pam]) or $(b,e[sy]). \
Defaults to $(b,opam). Only applicable for $(b,project) components."
in
opt_default
~default:Component.Options.Project.Pkg.Opam
Arg.(
value
& opt (some (enum Component.Options.Project.Pkg.commands)) None
& info [ "pkg" ] ~docv ~doc)
in
let name =
match common.public with
| None -> Dune_lang.Atom.to_string common.name
| Some public -> Dune_init.Public_name.to_string public
in
let context =
let init_context = Init_context.make path in
let root =
match path with
(* If a path is given, we use that for the root during project
initialization, creating the path to it if needed. *)
| Some path -> path
(* Otherwise we will use the project's given name, and create a
directory accordingly. *)
| None -> name
in
let builder = Builder.set_root common_builder root in
let (_ : Fpath.mkdir_p_result) = Fpath.mkdir_p root in
let common, config = Common.init builder in
let project_defaults = config.project_defaults in
Scheduler.go_with_rpc_server ~common ~config (fun () ->
Memo.run @@ init_context project_defaults)
in
Component.init
(Project { context; common; options = { template; inline_tests; pkg } });
print_completion "project" (Dune_lang.Atom.of_string name)
;;
let group =
let doc = "Command group for initializing Dune components." in
let synopsis =
Common.command_synopsis
[ "init project NAME [PATH] [OPTION]... "
; "init executable NAME [PATH] [OPTION]... "
; "init library NAME [PATH] [OPTION]... "
; "init test NAME [PATH] [OPTION]... "
]
in
let man =
[ `Blocks synopsis
; `S "DESCRIPTION"
; `P
{|$(b,dune init COMPONENT NAME [PATH] [OPTION]...) initializes a new dune
configuration for a component of the kind specified by the subcommand
$(b,COMPONENT), named $(b,NAME), with fields determined by the supplied
$(b,OPTION)s.|}
; `P
{|Run a subcommand with $(b, --help) for for details on it's supported arguments|}
; `P
{|If the optional $(b,PATH) is provided, it must be a path to a directory, and
the component will be created there. Otherwise, it is created in a child of the
current working directory, called $(b, NAME). To initialize a component in the
current working directory, use `.` as the $(b,PATH).|}
; `P
{|Any prefix of a $(b,COMMAND)'s name can be supplied in place of
full name (as illustrated in the synopsis).|}
; `P
{|For more details, see https://dune.readthedocs.io/en/stable/usage.html#initializing-components|}
; Common.examples
[ ( {|Generate a project skeleton for an executable named `myproj' in a
new directory named `myproj', depending on the bos library and
using inline tests along with ppx_inline_test |}
, {|dune init project myproj --libs bos --ppx ppx_inline_test --inline-tests|} )
; ( {|Configure an executable component named `myexe' in a dune file in the
current directory|}
, {|dune init executable myexe|} )
; ( {|Configure a library component named `mylib' in a dune file in the ./src
directory depending on the core and cmdliner libraries, the ppx_let
and ppx_inline_test preprocessors, and declared as using inline
tests|}
, {|dune init library mylib src --libs core,cmdliner --ppx ppx_let,ppx_inline_test --inline-tests|}
)
; ( {|Configure a test component named `mytest' in a dune file in the
./test directory that depends on `mylib'|}
, {|dune init test mytest test --libs mylib|} )
]
]
in
Cmd.group (Cmd.info "init" ~doc ~man) [ executable; project; library; test ]
;;
|