Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command line argument completion #669

Merged
merged 18 commits into from
Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
jsonrpc
(sha (>= 1.12))
cpu
arg-complete
(conf-gmp (>= 3)) ; only needed transitively, but they don't have lower bound, which is needed on MacOS
(conf-ruby :with-test)
(benchmark :with-test) ; TODO: make this optional somehow, (optional) on bench executable doesn't work
Expand Down
2 changes: 2 additions & 0 deletions goblint.opam
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ depends: [
"jsonrpc"
"sha" {>= "1.12"}
"cpu"
"arg-complete"
"conf-gmp" {>= "3"}
"conf-ruby" {with-test}
"benchmark" {with-test}
Expand Down Expand Up @@ -71,4 +72,5 @@ pin-depends: [
# [ "ppx_deriving_yojson.3.6.1" "git+https://github.com/ocaml-ppx/ppx_deriving_yojson.git#e030f13a3450e9cf7d2c43fa04e709ef608486cd" ]
# TODO: add back after release, only pinned for CI stability
[ "apron.v0.9.13" "git+https://github.com/antoinemine/apron.git#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11"]
[ "arg-complete.~dev" "git+https://github.com/sim642/ocaml-arg-complete.git#db6734111249a31b740f3d91e0dec77ec1c5b2e7"]
]
5 changes: 5 additions & 0 deletions goblint.opam.locked
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ bug-reports: "https://github.com/goblint/analyzer/issues"
depends: [
"angstrom" {= "0.15.0"}
"apron" {= "v0.9.13"}
"arg-complete" {= "~dev"}
"astring" {= "0.8.5" & with-doc}
"base-bigarray" {= "base"}
"base-bytes" {= "base"}
Expand Down Expand Up @@ -123,4 +124,8 @@ pin-depends: [
"ppx_deriving.5.2.1"
"git+https://github.com/ocaml-ppx/ppx_deriving.git#0a89b619f94cbbfc3b0fb3255ab4fe5bc77d32d6"
]
[
"arg-complete.~dev"
"git+https://github.com/sim642/ocaml-arg-complete.git#db6734111249a31b740f3d91e0dec77ec1c5b2e7"
]
]
1 change: 1 addition & 0 deletions goblint.opam.template
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ pin-depends: [
# [ "ppx_deriving_yojson.3.6.1" "git+https://github.com/ocaml-ppx/ppx_deriving_yojson.git#e030f13a3450e9cf7d2c43fa04e709ef608486cd" ]
# TODO: add back after release, only pinned for CI stability
[ "apron.v0.9.13" "git+https://github.com/antoinemine/apron.git#c852ebcc89e5cf4a5a3318e7c13c73e1756abb11"]
[ "arg-complete.~dev" "git+https://github.com/sim642/ocaml-arg-complete.git#db6734111249a31b740f3d91e0dec77ec1c5b2e7"]
]
15 changes: 15 additions & 0 deletions scripts/bash-completion.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

# Temporary usage:
# Run: source ./scripts/bash-completion.sh
#
# Permanent usage:
# Run: echo "source $(readlink -f .)/scripts/bash-completion.sh" >> ~/.bash_completion

_goblint ()
{
IFS=$'\n'
COMPREPLY=($(${COMP_WORDS[0]} --complete "${COMP_WORDS[@]:1:COMP_CWORD}"))
}

complete -o default -F _goblint goblint ./goblint
2 changes: 1 addition & 1 deletion src/dune
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
(public_name goblint.lib)
(wrapped false)
(modules :standard \ goblint mainarinc mainspec privPrecCompare apronPrecCompare)
(libraries goblint.sites goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu
(libraries goblint.sites goblint-cil.all-features batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete
; Conditionally compile based on whether apron optional dependency is installed or not.
; Alternative dependencies seem like the only way to optionally depend on optional dependencies.
; See: https://dune.readthedocs.io/en/stable/concepts.html#alternative-dependencies.
Expand Down
103 changes: 66 additions & 37 deletions src/maingoblint.ml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ let print_help ch =
exit 0

(** [Arg] option specification *)
let option_spec_list =
let add_string l = let f str = l := str :: !l in Arg.String f in
let add_int l = let f str = l := str :: !l in Arg.Int f in
let rec option_spec_list: Arg_complete.speclist Lazy.t = lazy (
let add_string l = let f str = l := str :: !l in Arg_complete.String (f, Arg_complete.empty) in
let add_int l = let f str = l := str :: !l in Arg_complete.Int (f, Arg_complete.empty) in
let set_trace sys =
if Messages.tracing then Tracing.addsystem sys
else (prerr_endline "Goblint has been compiled without tracing, recompile in trace profile (./scripts/trace_on.sh)"; raise Exit)
Expand All @@ -67,50 +67,79 @@ let option_spec_list =
set_bool "dbg.print_dead_code" true;
set_string "result" "sarif"
in
let complete_option_value option s =
let completions = List.assoc option Options.completions in
Arg_complete.strings completions s
in
let defaults_spec_list = List.map (fun path ->
(* allow "--option value" as shorthand for "--set option value" *)
("--" ^ path, Arg.String (set_auto path), "")
("--" ^ path, Arg_complete.String (set_auto path, complete_option_value path), "")
) Options.paths
in
let tmp_arg = ref "" in
[ "-o" , Arg.String (set_string "outfile"), ""
; "-v" , Arg.Unit (fun () -> set_bool "dbg.verbose" true; set_bool "printstats" true), ""
; "-j" , Arg.Int (set_int "jobs"), ""
; "-I" , Arg.String (set_string "pre.includes[+]"), ""
; "-IK" , Arg.String (set_string "pre.kernel_includes[+]"), ""
; "--set" , Arg.Tuple [Arg.Set_string tmp_arg; Arg.String (fun x -> set_auto !tmp_arg x)], ""
; "--sets" , Arg.Tuple [Arg.Set_string tmp_arg; Arg.String (fun x -> prerr_endline "--sets is deprecated, use --set instead."; set_string !tmp_arg x)], ""
; "--enable" , Arg.String (fun x -> set_bool x true), ""
; "--disable" , Arg.String (fun x -> set_bool x false), ""
; "--conf" , Arg.String merge_file, ""
; "--writeconf" , Arg.String (fun fn -> writeconffile := fn), ""
; "--version" , Arg.Unit print_version, ""
; "--print_options" , Arg.Unit (fun () -> Options.print_options (); exit 0), ""
; "--print_all_options" , Arg.Unit (fun () -> Options.print_all_options (); exit 0), ""
; "--trace" , Arg.String set_trace, ""
let last_complete_option = ref "" in
let complete_option s =
last_complete_option := s;
Arg_complete.strings Options.paths s
in
let complete_bool_option s =
let cs = complete_option s in
let is_bool c =
match GobConfig.get_json c with
| `Bool _ -> true
| _ -> false
in
List.filter is_bool cs
in
let complete_last_option_value s =
complete_option_value !last_complete_option s
in
[ "-o" , Arg_complete.String (set_string "outfile", Arg_complete.empty), ""
; "-v" , Arg_complete.Unit (fun () -> set_bool "dbg.verbose" true; set_bool "printstats" true), ""
; "-j" , Arg_complete.Int (set_int "jobs", Arg_complete.empty), ""
; "-I" , Arg_complete.String (set_string "pre.includes[+]", Arg_complete.empty), ""
; "-IK" , Arg_complete.String (set_string "pre.kernel_includes[+]", Arg_complete.empty), ""
; "--set" , Arg_complete.Tuple [Arg_complete.Set_string (tmp_arg, complete_option); Arg_complete.String ((fun x -> set_auto !tmp_arg x), complete_last_option_value)], ""
; "--sets" , Arg_complete.Tuple [Arg_complete.Set_string (tmp_arg, complete_option); Arg_complete.String ((fun x -> prerr_endline "--sets is deprecated, use --set instead."; set_string !tmp_arg x), complete_last_option_value)], ""
; "--enable" , Arg_complete.String ((fun x -> set_bool x true), complete_bool_option), ""
; "--disable" , Arg_complete.String ((fun x -> set_bool x false), complete_bool_option), ""
; "--conf" , Arg_complete.String (merge_file, Arg_complete.empty), ""
; "--writeconf" , Arg_complete.String ((fun fn -> writeconffile := fn), Arg_complete.empty), ""
; "--version" , Arg_complete.Unit print_version, ""
; "--print_options" , Arg_complete.Unit (fun () -> Options.print_options (); exit 0), ""
; "--print_all_options" , Arg_complete.Unit (fun () -> Options.print_all_options (); exit 0), ""
; "--trace" , Arg_complete.String (set_trace, Arg_complete.empty), ""
; "--tracevars" , add_string Tracing.tracevars, ""
; "--tracelocs" , add_int Tracing.tracelocs, ""
; "--help" , Arg.Unit (fun _ -> print_help stdout),""
; "--html" , Arg.Unit (fun _ -> configure_html ()),""
; "--sarif" , Arg.Unit (fun _ -> configure_sarif ()),""
; "--compare_runs" , Arg.Tuple [Arg.Set_string tmp_arg; Arg.String (fun x -> set_auto "compare_runs" (sprintf "['%s','%s']" !tmp_arg x))], ""
; "--oil" , Arg.String oil, ""
(* ; "--tramp" , Arg.String (set_string "ana.osek.tramp"), "" *)
; "--osekdefaults" , Arg.Unit (fun () -> set_bool "ana.osek.defaults" false), ""
; "--osektaskprefix" , Arg.String (set_string "ana.osek.taskprefix"), ""
; "--osekisrprefix" , Arg.String (set_string "ana.osek.isrprefix"), ""
; "--osektasksuffix" , Arg.String (set_string "ana.osek.tasksuffix"), ""
; "--osekisrsuffix" , Arg.String (set_string "ana.osek.isrsuffix"), ""
; "--osekcheck" , Arg.Unit (fun () -> set_bool "ana.osek.check" true), ""
; "--oseknames" , Arg.Set_string OilUtil.osek_renames, ""
; "--osekids" , Arg.Set_string OilUtil.osek_ids, ""
; "--help" , Arg_complete.Unit (fun _ -> print_help stdout),""
; "--html" , Arg_complete.Unit (fun _ -> configure_html ()),""
; "--sarif" , Arg_complete.Unit (fun _ -> configure_sarif ()),""
; "--compare_runs" , Arg_complete.Tuple [Arg_complete.Set_string (tmp_arg, Arg_complete.empty); Arg_complete.String ((fun x -> set_auto "compare_runs" (sprintf "['%s','%s']" !tmp_arg x)), Arg_complete.empty)], ""
; "--oil" , Arg_complete.String (oil, Arg_complete.empty), ""
(* ; "--tramp" , Arg_complete.String (set_string "ana.osek.tramp"), "" *)
; "--osekdefaults" , Arg_complete.Unit (fun () -> set_bool "ana.osek.defaults" false), ""
; "--osektaskprefix" , Arg_complete.String (set_string "ana.osek.taskprefix", Arg_complete.empty), ""
; "--osekisrprefix" , Arg_complete.String (set_string "ana.osek.isrprefix", Arg_complete.empty), ""
; "--osektasksuffix" , Arg_complete.String (set_string "ana.osek.tasksuffix", Arg_complete.empty), ""
; "--osekisrsuffix" , Arg_complete.String (set_string "ana.osek.isrsuffix", Arg_complete.empty), ""
; "--osekcheck" , Arg_complete.Unit (fun () -> set_bool "ana.osek.check" true), ""
; "--oseknames" , Arg_complete.Set_string (OilUtil.osek_renames, Arg_complete.empty), ""
; "--osekids" , Arg_complete.Set_string (OilUtil.osek_ids, Arg_complete.empty), ""
; "--complete" , Arg_complete.Rest_all_compat.spec (Lazy.force rest_all_complete), ""
] @ defaults_spec_list (* lowest priority *)

)
and rest_all_complete = lazy (Arg_complete.Rest_all_compat.create complete Arg_complete.empty_all)
and complete args =
Arg_complete.complete_argv args (Lazy.force option_spec_list) Arg_complete.empty
|> List.iter print_endline;
raise Exit

(** Parse arguments. Print help if needed. *)
let parse_arguments () =
let anon_arg = set_string "files[+]" in
Arg.parse option_spec_list anon_arg "Look up options using 'goblint --help'.";
let arg_speclist = Arg_complete.arg_speclist (Lazy.force option_spec_list) in
Arg.parse arg_speclist anon_arg "Look up options using 'goblint --help'.";
Arg_complete.Rest_all_compat.finish (Lazy.force rest_all_complete);
if !writeconffile <> "" then (GobConfig.write_file !writeconffile; raise Exit);
if get_string_list "files" = [] then (
prerr_endline "No files for Goblint?";
Expand Down Expand Up @@ -202,8 +231,8 @@ let preprocess_files () =
(* linux-headers not installed with goblint package *)
]
in
let kernel_root =
try List.find Sys.file_exists kernel_roots
let kernel_root =
try List.find Sys.file_exists kernel_roots
with Not_found -> prerr_endline "Root directory for kernel include files not found!"; raise Exit
in

Expand Down
3 changes: 3 additions & 0 deletions src/util/gobConfig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ module ValidatorRequireAll = JsonSchema.Validator (struct let schema = Options.r
(** The type for [gobConfig] module. *)
module type S =
sig
val get_json: string -> Yojson.Safe.t

(** Functions to query conf variable of type int. *)
val get_int : string -> int

Expand Down Expand Up @@ -281,6 +283,7 @@ struct
eprintf "Cannot find value '%s' in\n%t\nDid You forget to add default values to options.schema.json?\n"
st print;
failwith "get_path_string"
let get_json = get_path_string Fun.id

(** Convenience functions for reading values. *)
(* memoize for each type with BatCache: *)
Expand Down
42 changes: 42 additions & 0 deletions src/util/options.ml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,48 @@ let schema_paths (schema: schema): string list =

let paths = schema_paths schema

let rec element_completions (element: element): (string * string list) list =
let default_completion () =
match element.default with
| Some default ->
[("", [Yojson.Safe.to_string (Json_repr.any_to_repr (module Json_repr.Yojson) default)])]
| None ->
[("", [])]
in
match element.kind with
| Integer _
| Number _
| Monomorphic_array _ ->
default_completion ()
| Boolean ->
[("", ["false"; "true"])]
| String string_specs ->
begin match element.enum with
| None ->
default_completion ()
| Some enum ->
let cs = List.map (fun value ->
match Json_repr.any_to_repr (module Json_repr.Yojson) value with
| `String value -> value
| _ -> failwith "element_completions: string_enum"
) enum
in
[("", cs)]
end
| Object object_specs ->
List.concat_map (fun (name, field_element, _, _) ->
List.map (fun (path, cs) -> (name ^ "." ^ path, cs)) (element_completions field_element)
) object_specs.properties
| _ ->
Format.printf "%a\n" Json_schema.pp (create element);
failwith "element_completions"

let schema_completions (schema: schema): (string * string list) list =
element_completions (root schema)
|> List.map (BatTuple.Tuple2.map1 BatString.rchop) (* remove trailing '.' *)

let completions = schema_completions schema

let rec pp_options ~levels ppf (element: element) =
match element.kind with
| String _
Expand Down