command_def.ml 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. (******************************************************************************)
  2. (* Copyright © Joly Clément, 2014-2015 *)
  3. (* *)
  4. (* leowzukw@oclaunch.eu.org *)
  5. (* *)
  6. (* Ce logiciel est un programme informatique servant à exécuter *)
  7. (* automatiquement des programmes à l'ouverture du terminal. *)
  8. (* *)
  9. (* Ce logiciel est régi par la licence CeCILL soumise au droit français et *)
  10. (* respectant les principes de diffusion des logiciels libres. Vous pouvez *)
  11. (* utiliser, modifier et/ou redistribuer ce programme sous les conditions *)
  12. (* de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA *)
  13. (* sur le site "http://www.cecill.info". *)
  14. (* *)
  15. (* En contrepartie de l'accessibilité au code source et des droits de copie, *)
  16. (* de modification et de redistribution accordés par cette licence, il n'est *)
  17. (* offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons, *)
  18. (* seule une responsabilité restreinte pèse sur l'auteur du programme, le *)
  19. (* titulaire des droits patrimoniaux et les concédants successifs. *)
  20. (* *)
  21. (* A cet égard l'attention de l'utilisateur est attirée sur les risques *)
  22. (* associés au chargement, à l'utilisation, à la modification et/ou au *)
  23. (* développement et à la reproduction du logiciel par l'utilisateur étant *)
  24. (* donné sa spécificité de logiciel libre, qui peut le rendre complexe à *)
  25. (* manipuler et qui le réserve donc à des développeurs et des professionnels *)
  26. (* avertis possédant des connaissances informatiques approfondies. Les *)
  27. (* utilisateurs sont donc invités à charger et tester l'adéquation du *)
  28. (* logiciel à leurs besoins dans des conditions permettant d'assurer la *)
  29. (* sécurité de leurs systèmes et ou de leurs données et, plus généralement, *)
  30. (* à l'utiliser et l'exploiter dans les mêmes conditions de sécurité. *)
  31. (* *)
  32. (* Le fait que vous puissiez accéder à cet en-tête signifie que vous avez *)
  33. (* pris connaissance de la licence CeCILL, et que vous en avez accepté les *)
  34. (* termes. *)
  35. (******************************************************************************)
  36. open Core.Std;;
  37. open Command;;
  38. (* Module containing the definition of the interface of OcLaunch *)
  39. (* Type to return result of the work with common arguments *)
  40. type return_arg = {
  41. rc : Settings_t.rc_file Lazy.t;
  42. }
  43. (* Shorthand *)
  44. let id_seq = Id_parsing.id_sequence;;
  45. let iter_seq = Id_parsing.helper;;
  46. (* A set of default arguments, usable with most of the commands *)
  47. let shared_params =
  48. let open Param in
  49. (* Way to treat common args *)
  50. return (fun verbosity assume_yes no_color rc_file_name handle_signal ->
  51. (* Set the level of verbosity *)
  52. Const.verbosity := verbosity;
  53. (* Ask question or not, see Const.ask for details *)
  54. Const.ask := Option.(
  55. merge
  56. (some_if assume_yes true)
  57. !Const.ask
  58. ~f:( || )
  59. );
  60. (* Do not use color *)
  61. Const.no_color := no_color || !Const.no_color;
  62. (* Use given rc file, preserving lazyness, since Const.rc_file is not
  63. * yet evaluated *)
  64. Const.rc_file :=
  65. Option.value_map ~f:(fun rfn -> Lazy.return rfn)
  66. ~default:!Const.rc_file rc_file_name
  67. ;
  68. (* Active signal handling *)
  69. if handle_signal then
  70. Signals.handle ();
  71. (* Debugging *)
  72. let d = Messages.debug in
  73. d (sprintf "Verbosity set to %i" !Const.verbosity);
  74. d (match !Const.ask with
  75. | None -> "Assume nothing"
  76. | Some false -> "Assume No"
  77. | Some true -> "Assume Yes");
  78. d (sprintf "Color %s" (match !Const.no_color with true -> "off" | false -> "on"));
  79. begin
  80. match Option.try_with (fun () -> Lazy.force !Const.rc_file) with
  81. | None -> d "Configuration file will fail if used";
  82. | Some rc -> d (sprintf "Configuration file is %s" rc);
  83. end;
  84. d (sprintf "Tmp file is %s" Const.tmp_file);
  85. (* Obtain data from rc_file *)
  86. d "Reading rc_file...";
  87. let rc_content = lazy (File_com.init_rc ()) in
  88. d "Read rc_file";
  89. { rc = rc_content } (* We use type for futur use *)
  90. )
  91. (* Flag to set verbosity level *)
  92. <*> flag "-v" (optional_with_default !Const.verbosity int)
  93. ~aliases:["--verbose" ; "-verbose"]
  94. ~doc:"[n] Set verbosity level. \
  95. The higher n is, the most verbose the program is."
  96. (* Flag to assume yes *)
  97. <*> flag "-y" no_arg
  98. ~aliases:["--yes" ; "-yes"]
  99. ~doc:" Assume yes, never ask anything. \
  100. Setting OC_YES environment variable to '1' is the same. \
  101. Set it to '0' to assume no. \
  102. Set it to '-1' to be asked every time."
  103. (* Flag to set colors *)
  104. <*> flag "--no-color" no_arg
  105. ~aliases:["-no-color"]
  106. ~doc:" Use this flag to disable color usage."
  107. (* Flag to use different rc file *)
  108. <*> flag "-c" (optional file)
  109. ~aliases:["--rc" ; "-rc"]
  110. ~doc:"file Read configuration from the given file and continue parsing."
  111. (* Flag to handle signals *)
  112. <*> flag "-s" no_arg
  113. ~aliases:["--signals" ; "-signals"]
  114. ~doc:" Handle signals. Warning, this is not much tested and not \
  115. implemented the best way."
  116. ;;
  117. (* Comman documentation for id sequences *)
  118. let id_parsing_doc =
  119. ". Id sequence means a set of ids like this: 1,4-9,17. The command is run \
  120. for each item of the generated list (1,4,5,6,7,8,9,17 in this case)"
  121. (* basic-commands *)
  122. (* To reset tmp file *)
  123. let reset =
  124. basic
  125. ~summary:("Reinitialises launches for the command number [command] to [n]. \
  126. With both the [command] and the [n] argumennt, the command number \
  127. [command] is resetted to [n]. \
  128. With only the [n] argument, every entry in current tmp file is \
  129. resetted to [n]. [command] may be a sequence of ids"
  130. ^ id_parsing_doc)
  131. Spec.(
  132. empty
  133. +> shared_params
  134. +> anon ("target_number" %: int)
  135. +> anon (maybe ("command_number" %: id_seq))
  136. )
  137. (fun { rc } num cmd () ->
  138. (* Call the right function, according to optionnal argument.
  139. * Since it's arguments passed on command line, we can not have
  140. * num = None
  141. * cmd = Some n
  142. * cmd: number of the command to be reseted
  143. * num: number to reset *)
  144. let rc = Lazy.force rc in
  145. match ( num, cmd ) with
  146. | ( num, None ) | ( num, Some [] ) -> Tmp_file.reset2num ~rc num
  147. | ( num, Some cmd_list ) ->
  148. List.iter ~f:(fun cmd -> Tmp_file.reset_cmd ~rc num cmd) cmd_list
  149. )
  150. ;;
  151. let reset_all =
  152. basic
  153. ~summary:" Reinitialises launches for everything."
  154. Spec.(
  155. empty
  156. +> shared_params
  157. )
  158. (fun { rc } () ->
  159. Tmp_file.reset_all ()
  160. )
  161. ;;
  162. (* To list each commands with its number *)
  163. let list =
  164. basic
  165. ~summary:"Print a list of all commands with their number. Useful to launch with number. \
  166. Displays a star next to next command to launch."
  167. Spec.(
  168. empty
  169. +> shared_params
  170. +> flag "-l" (optional int)
  171. ~aliases:[ "--length" ; "-length" ; "--elength" ; "-elength" ]
  172. ~doc:" Max length of displayed entries, 0 keeps as-is"
  173. )
  174. (fun { rc } elength () ->
  175. let rc = Lazy.force rc in
  176. List_rc.run ~rc ?elength ())
  177. ;;
  178. (* To clean-up rc file *)
  179. let clean =
  180. basic
  181. ~summary:"Remove doubled entries, trailing spaces in them... \
  182. Useful after manual editing or with rc file from old version."
  183. Spec.(
  184. empty
  185. +> shared_params
  186. )
  187. (fun { rc } () ->
  188. let rc = Lazy.force rc in
  189. Clean_command.run ~rc ()
  190. )
  191. ;;
  192. (* To add a command to rc file, from stdin or directly *)
  193. let add =
  194. basic
  195. ~summary:("Add the command given on stdin to the configuration file at a \
  196. given position(s) ([id_sequence]). If nothing is given, or if \
  197. it is out of bound, append commands at the end"
  198. ^ id_parsing_doc)
  199. Spec.(
  200. empty
  201. +> shared_params
  202. +> anon (maybe ("id_sequence" %: id_seq))
  203. )
  204. (fun { rc } cmd_seq () ->
  205. let rc = Lazy.force rc in
  206. iter_seq ~f:(fun num_cmd -> Add_command.run ~rc num_cmd) cmd_seq
  207. )
  208. ;;
  209. (* To remove a command from rc file *)
  210. let delete =
  211. basic
  212. ~summary:("Remove the [COMMAND_NUMBER]th command from configuration file. \
  213. If [COMMAND_NUMBER] is absent, remove last one. \n\
  214. [COMMAND_NUMBER] may be a sequence of ids"
  215. ^ id_parsing_doc)
  216. Spec.(
  217. empty
  218. +> shared_params
  219. +> anon (maybe ("command_number" %: id_seq))
  220. )
  221. (fun { rc } cmd_seq () ->
  222. let rc = Lazy.force rc in
  223. iter_seq
  224. ~f:(fun num_cmd ->
  225. Remove_command.run ~rc num_cmd) cmd_seq)
  226. ;;
  227. (* To display current state *)
  228. let state =
  229. basic
  230. ~summary:"Display current state of the program."
  231. Spec.(
  232. empty
  233. +> shared_params
  234. )
  235. (fun { rc } () ->
  236. let rc = Lazy.force rc in
  237. State.print_current ~rc ())
  238. ;;
  239. (* To edit the nth command *)
  240. let edit =
  241. basic
  242. ~summary:("Edit the [COMMAND_NUMBER]th command of the rc file in your \
  243. $EDITOR. May be used to add new entries, without argument, one new \
  244. command per line. \n\
  245. [COMMAND_NUMBER] may be a sequence of ids"
  246. ^ id_parsing_doc)
  247. Spec.(
  248. empty
  249. +> shared_params
  250. +> anon (maybe ("command_number" %: id_seq))
  251. )
  252. (fun { rc } cmd_seq () ->
  253. let rc = Lazy.force rc in
  254. iter_seq cmd_seq ~f:(fun n ->
  255. let position =
  256. Option.value n
  257. ~default:(List.length (rc.Settings_t.progs) - 1)
  258. in
  259. Edit_command.run ~rc position)
  260. )
  261. ;;
  262. (* To display informations about the licence *)
  263. let licence =
  264. basic
  265. ~summary:"Display the licence of the program"
  266. Spec.(
  267. empty
  268. +> shared_params
  269. +> flag "-header" no_arg
  270. ~doc:" Display the header associated to the licence"
  271. )
  272. (fun _ header () ->
  273. (* When cecill is false, it displays the header *)
  274. let cecill = not(header) in
  275. Licencing.print ~cecill
  276. )
  277. ;;
  278. (* Run nth command, default use *)
  279. let default =
  280. basic
  281. ~summary:("Run the id sequence" ^ id_parsing_doc)
  282. Spec.(
  283. empty
  284. +> shared_params
  285. +> anon (maybe ("command_number" %: id_seq))
  286. )
  287. (fun { rc } cmd_seq () ->
  288. let rc = Lazy.force rc in
  289. iter_seq cmd_seq ~f:(fun n -> Default.run ~rc n)
  290. )
  291. let run ~version ~build_info () =
  292. (* Store begin time *)
  293. let start = Time.now () in
  294. (* XXX Hack to allow to run 'oclaunch 5' or 'oclaunch' as before, i.e. do not
  295. * display help for sub commands but use the program directly *)
  296. let hack_parse () =
  297. let run_default () =
  298. default
  299. |> run ~version ~build_info
  300. in
  301. match Sys.argv with
  302. | [| _ |] -> Result.Ok (run_default ()) (* Program called with nothing *)
  303. | _ -> (* Program followed by a number *)
  304. Or_error.try_with (fun () ->
  305. (* Verify the fist argument is a number, not a subcommand (string) *)
  306. ignore (Int.of_string (Sys.argv.(1)));
  307. run_default ())
  308. in
  309. (* Parsing with subcommands *)
  310. let parse_sub () =
  311. group
  312. ~summary:"OcLaunch program is published under CeCILL licence.\n\
  313. You may run the program with 'licence' command or see \
  314. http://cecill.info/licences/Licence_CeCILL_V2.1-en.html \
  315. (https://lnch.ml/cecill) for details. More here: \
  316. https://oclaunch.eu.org/floss-under-cecill (https://lnch.ml/l)."
  317. ~readme:(fun () -> File_com.welcome_msg)
  318. ~preserve_subcommand_order:()
  319. [ ("run", default) ; ("licence", licence) ; ("add", add) ; ("edit", edit)
  320. ; ("list", list) ; ("cleanup", clean) ; ("delete", delete)
  321. ; ("state", state) ; ( "reset", reset) ; ( "reset-all", reset_all) ]
  322. |> run ~version ~build_info
  323. in
  324. (* Return error code with exceptions *)
  325. let exit_code =
  326. match
  327. hack_parse ()
  328. |> (function
  329. Result.Ok () -> ()
  330. | Error _ -> parse_sub ())
  331. with
  332. | () -> `Exit 0
  333. (* XXX Command.basic function catch exceptions, so this doesn't actually
  334. * work. We may to place it before basic function to fix the problem. *)
  335. | exception message ->
  336. "Exception: " ^ (Exn.to_string message)
  337. |> Messages.warning;
  338. Bug.report ();
  339. `Exit 20
  340. in
  341. (* Unlock, should be done before *)
  342. Lock.(status ()
  343. |> (function
  344. Locked ->
  345. Messages.warning "Removing lockfile, should be removed before. \
  346. It's a bug!"; remove ()
  347. | Free -> ()
  348. | Error -> Messages.warning "Error with lockfile"
  349. ));
  350. (* Display total running time, pretty printing is handled by Time module *)
  351. Messages.debug Time.(diff (now ()) start
  352. |> Span.to_string_hum (* Round the value, 3 digits *)
  353. |> sprintf "Duration: %s");
  354. (* Reset display *)
  355. Messages.reset ();
  356. exit_code
  357. ;;