Global CLI flag in OCaml Core.Command - ocaml

I am currently creating a CLI application in OCaml and using Core.Command, the CLI parser included in core (v0.10), to parse the command line.
I want to have a global flag that can be used for any subcommand (like the --paginate or --git-dir flags in git for example).
For instance, I want a -debug flag so that the two following commands are valid
my-cli -debug hello world
my-cli -debug goodbye world
However, I could not find a way to do this with the Core.Command API.
Here is a simplified version what I currently have.
open Core
let initialize_logger debug =
Logs.set_reporter (Logs_fmt.reporter ());
let log_level = if debug then Logs.Debug else Logs.Info in
Logs.set_level (Some log_level)
let some_func_with_logging () =
Logs.debug (fun m -> m "the flag debug was passed!")
let hello name =
some_func_with_logging ();
Printf.printf "Hello %s!\n" name
let goodbye name =
some_func_with_logging ();
Printf.printf "Goodbye %s!\n" name
let hello_command =
let open Command.Let_syntax in
Command.basic
~summary:"says hello"
[%map_open
let name = anon ("name" %: string)
and debug = flag "debug" no_arg ~doc:"debug" in
fun () ->
initialize_logger debug;
hello name
]
let goodbye_command =
let open Command.Let_syntax in
Command.basic
~summary:"says goodbye"
[%map_open
let name = anon ("name" %: string)
and debug = flag "debug" no_arg ~doc:"debug" in
fun () ->
initialize_logger debug;
goodbye name
]
let main_command =
Command.group ~summary:"a cool CLI tool"
[ ("hello", hello_command);
("goodbye", goodbye_command);
]
let () = Command.run main_command
There are two main issues here:
the debug flag as well as the call to initialize_logger is duplicated in every subcommand
the debug flag needs to be passed after the subcommand when invoking the command: my-cli hello world -debug instead of my-cli -debug hello world
Is there a clean way to handle this with Core.Command API?

Related

How are command line argument are treated in toplevel?

I have a program which takes command line argument. The same of source file is encode.ml. I want to load this file in the toplevel.
Is there way to load the source file in the toplevel where we can pass it a command line arguments?
Thanks.
Yes, invoke your toplevel with ocaml encode.ml arg1 arg2 etc. The following program demonstrates it:
$ cat args.ml
let () =
Array.iteri (Printf.printf "%d -> %s\n") Sys.argv
$ ocaml args.ml -h --help -help
0 -> args.ml
1 -> -h
2 -> --help
3 -> -help

ocaml-core equivalent of Unix.create_process

I'd like to port the following command from Unix library to Jane Street's Core.Std.Unix library.
Unix.create_process exec args Unix.stdin Unix.stdout Unix.stderr
That is, I have an executable exec and arguments args and want to run the process using the same in/out/error channels as the current process.
I can get close with Core.Std.Unix.create_process ~exec:exec ~args:args, but can't figure out how to connect the stdin,stdout,stderr returned by this function from core with the file descriptors used by the current process.
You can dup2 the returned descriptors to your current descriptors, but I'm not sure that this would work.
open Core.Std
let redirect ( p : Unix.Process_info.t ) : unit =
let open Unix.Process_info in
List.iter ~f:(fun (src,dst) -> Unix.dup2 ~src ~dst) [
p.stdin, Unix.stdin;
p.stdout, Unix.stdout;
p.stderr, Unix.stderr
]
let exec prog args : unit =
let p = Unix.create_process ~prog ~args in
redirect p
But there is another solution, that maybe applicable. Consider using just Unix.system, it should work just out of box.
I'm not sure if I understand your question correctly, but if you just want to use the same channels as the current process, take a look at fork_exec in Core.Std.Unix:
val fork_exec : prog:string ->
args:string list ->
?use_path:bool ->
?env:string list ->
unit -> Core_kernel.Std.Pid.t
According to the docs,
fork_exec ~prog ~args ?use_path ?env () forks and execs prog with args in the child process, returning the child pid to the parent.

Redirect standard output OCaml

How can you redirect the standard output in OCaml ?
I tried Format.set_formatter_out_channel but it doesn't seem to work. When I use printf afterwards, the text is still printed on the screen, and the file I created remains empty.
The reason your experiment failed is that Printf.printf doesn't use the output channel of the Format module. The Format module is for pretty-printing, a fairly elaborate task. The Printf.printf function writes formatted data to the standard output (a C-style printf).
Do you really want to redirect standard output, or do you just want to write to a specific channel? To write to a channel oc you can just use
Printf.fprintf oc ...
rather than
Printf.printf ...
Doing redirection is a different thing. You can do it with Unix.dup2. Here's an example session that shows how to do it:
$ cat redirected
cat: redirected: No such file or directory
$ cat redir.ml
let main () =
let newstdout = open_out "redirected" in
Unix.dup2 (Unix.descr_of_out_channel newstdout) Unix.stdout;
Printf.printf "line of text\n";
Printf.printf "second line of text\n"
let () = main ()
$ ocamlopt -o redir unix.cmxa redir.ml
$ ./redir
$ cat redirected
line of text
second line of text
Since this is changing low-level file descriptors behind the back of the OCaml I/O system, I'd be a little careful. As a quick hack it's fantastic--I've done it many times.
Update
Here's a version of the above code that redirects standard output temporarily, then puts it back where it was before.
$ cat redirected
cat: redirected: No such file or directory
$
$ cat redir.ml
let main () =
let oldstdout = Unix.dup Unix.stdout in
let newstdout = open_out "redirected" in
Unix.dup2 (Unix.descr_of_out_channel newstdout) Unix.stdout;
Printf.printf "line of text\n";
Printf.printf "second line of text\n";
flush stdout;
Unix.dup2 oldstdout Unix.stdout;
Printf.printf "third line of text\n";
Printf.printf "fourth line of text\n"
let () = main ()
$
$ ocamlopt -o redir unix.cmxa redir.ml
$ ./redir
third line of text
fourth line of text
$
$ cat redirected
line of text
second line of text

How do i compile something with Pgocaml

I'm trying to use Pgocaml for database interactions within my application.
This is the file I'm trying to compile:
let () =
let dbh = PGOCaml.connect () in
let insert name salary email =
PGSQL(dbh) "insert into employees (name, salary, email) values ($name, $salary, $?email)"
in
insert "Ann" 10_000_l None;
insert "Bob" 45_000_l None;
insert "Jim" 20_000_l None;
insert "Mary" 30_000_l (Some "mary#example.com");
let print_row (id, name, salary, email) =
let email = match email with Some email -> email | None -> " -"
in Printf.printf "%ld %S %ld %S\n" id name salary email in
let rows =
PGSQL(dbh) "select id, name, salary, email from employees"
in List.iter print_row rows;
PGOCaml.close dbh
This is how I am trying to compile it:
ocamlbuild -use-ocamlfind -pkg pgocaml pgex.native
and this is the error I am getting:
+ ocamlfind ocamldep -package pgocaml -modules pgex.ml > pgex.ml.depends
File "pgex.ml", line 4, characters 19-97:
Error: Syntax error
Command exited with code 2.
Why am I getting this error?
Thanks in advance!
PG'OCaml is a syntax extension, it is not a regular ocaml code, so you need to take extra steps to let it perform its magic. First, read the tutorial which also explains how to compile projects with pgocaml. Second, tell the build system that pgex.ml should be preprocessed with camlp4 - i.e. create _tags file with the contents <pgex.ml>: syntax(camlp4o).

Can't find namespace for method(16:method)-while client using SOAP::Lite

When I run my perl script (version: 5.6.1), I got below error:
Can't find namespace for method(16:method)
my code was:
my $ws_url = '$url';
my $ws_uri = '$uri';
my $ws_xmlns = '$xmlns';
my $soap = SOAP::Lite
-> uri($ws_uri)
-> on_action( sub { join '/s/', $ws_uri, $_[1] } )
-> proxy($ws_url);
my $method = SOAP::Data->name('Add')
->attr({xmlns => $ws_xmlns});
my #params = ( SOAP::Data->name(addParam => $myParam));
$response = $soap->call($method => #params);
then I read documentation in link:
http://docs.activestate.com/activeperl/5.8/lib/SOAP/Lite.html, which says:
Be warned, that though you have more control using this method, you should
specify namespace attribute for method explicitely, even if you made uri()
call earlier. So, if you have to have namespace on method element, instead of:
print SOAP::Lite
-> new(....)
-> uri('mynamespace') # will be ignored
-> call(SOAP::Data->name('method') => #parameters)
-> result;
do
print SOAP::Lite
-> new(....)
-> call(SOAP::Data->name('method')->attr({xmlns => 'mynamespace'})
=> #parameters)
-> result;
……….
……….
Moreover, it'll become fatal error if you try to call it with prefixed name:
print SOAP::Lite
-> new(....)
-> uri('mynamespace') # will be ignored
-> call(SOAP::Data->name('a:method') => #parameters)
-> result;
gives you:
Can't find namespace for method (a:method)
because nothing is associated with prefix 'a'.
So, I tried change my code to:
my $soap = SOAP::Lite
-> on_action( sub { join '/s/', $ws_uri, $_[1] } )
-> proxy($ws_url);
my #params = ( SOAP::Data->name(addParam => $myParam));
my $response = $soap->call(SOAP::Data->name('Add')->attr({xmlns => $ws_xmlns}) => #params)
->result;
and it still did not work..
any advice?
thanks ahead!
SOAP::Lite uses Scalar::Util. Scalar::Util has XS (ie, compiled C, non-pure-Perl) code in it.
The Perl version you are running with is 5.6.1.
The documentation link you provided points to an ActiveState library for Perl version 5.8.0. I'm going to assume that the version of SOAP::Lite you installed was compiled for use with 5.8.0, since that's the documentation version you cited.
Perl version 5.8.0 is not binary compatible with Perl 5.6.1. Modules compiled for 5.6.1 that contain XS will not run under 5.8.0. Modules compiled for 5.8.0 that contain XS code will not run under 5.6.1. In your case, it's not the module SOAP::Lite that contains the XS code, but one of its dependencies: Scalar::Util.
When you installed SOAP::Lite from the ActiveState repository for 5.8.0, PPM updated all of the module's dependencies, including Scalar::Util. In so doing, it installed a version of Scalar::Util that is not binary compatible with Perl 5.6.1.
The error you're experiencing is sufficiently wonky to support this theory, in the absence of a better one. Seems like the easiest way out of the mess would be to upgrade Perl, as well as your installed modules, and hope it doesn't break something else. ;)