Search code examples
ocamlcommand-line-argumentsrecord

OCaml illiterate in need of record manipulation assistance


This is a little embarrassing but I've been shown an OCaml program where I know what I want to do but I cannot find the documentation to help me write what I want in the right syntax as I've never used the language before.

I made quite a few working changes to the program and this happens to be the last change I need to make and I cannot work it out!

There is an record of a sort (the syntax used had thrown me off) named caps_default:

let caps_default =
    {
        browserName = Firefox ;
        version = "" ;
        platform = ANY ;
        username = "" ;
        accessKey = "" ;
        javascriptEnabled = true
    }
;;

Then these variables are defined to receive the command line arguments:

exception Incorrect_cmdline of string option
;;

let browser_name   = ref ""
and hub_url_opt    = ref ""
and target_url_opt = ref ""
and xml_out_string = ref ""
and user_name      = ref ""
and access_key     = ref ""
;;

I added two new arguments named 'username' and 'accessKey' and I want to add them to a "caps" record thing.

Continuing on, here the command line arguments are parsed:

let (target_url, hub_url, caps, xml_out) =
    try
        parse_cmdline
            [
                ( noshort , "browser-name" , None, (atmost_once browser_name   (Incorrect_cmdline (Some "browser-name"))) ) ;
                ( noshort , "hub-url"      , None, (atmost_once hub_url_opt    (Incorrect_cmdline (Some "hub-url"))) ) ;
                ( noshort , "target-url"   , None, (atmost_once target_url_opt (Incorrect_cmdline (Some "target-url"))) ) ;
                ( noshort , "xml-output"   , None, (atmost_once xml_out_string (Incorrect_cmdline (Some "xml-output"))) ) ;
                ( noshort , "username"     , None, (atmost_once user_name      (Incorrect_cmdline (Some "username"))) ) ;
                ( noshort , "accessKey"    , None, (atmost_once access_key     (Incorrect_cmdline (Some "accessKey"))) ) ;
                ( noshort , "help"         , Some (fun s -> raise (Incorrect_cmdline None)) , None );
            ]
            ( fun x -> raise (Incorrect_cmdline None)) ;
        ignore (List.map
                (fun s -> if BatString.is_empty !s then raise (Incorrect_cmdline None) else () )
                [ browser_name ; hub_url_opt ; target_url_opt ] );
        let target_url =
            try
            Neturl.parse_url !target_url_opt
            with
            Neturl.Malformed_URL ->
            raise (Incorrect_cmdline (Some ("Invalid target url: " ^ !target_url_opt)))
        and hub_url =
            try
            Neturl.parse_url !hub_url_opt
            with
            Neturl.Malformed_URL ->
            raise (Incorrect_cmdline (Some ("Invalid hub url: " ^ !hub_url_opt)))
        and xml_out =
            if BatString.is_empty !xml_out_string
            then fun s -> ignore s
            else
            let chan = open_out !xml_out_string in
            (fun xml ->
            output_string chan (Xml.to_string xml) ;
            close_out chan )
        (* Continued below *)

My desire is to take my new command line argument options username and accessKey, assigned to the variables user_name and access_key, to be transmitted to the caps record that is defined in the let assignments below:

        (* Continued from above *)
        and caps =
            { caps_default with username = !user_name }   (* My addition *)
            { caps_default with accessKey = !access_key } (* My addition *)
            match !browser_name with   (* This switch is original and worked *)
            | "firefox"   -> { caps_default with browserName = Firefox }
            | "chrome"    -> { caps_default with browserName = Chrome }
            | "html_unit" -> { caps_default with browserName = HtmlUnit }
            | "ie"        -> { caps_default with browserName = InternetExplorer }
            | "iphone"    -> { caps_default with browserName = IPhone }
            |  _        -> raise (Incorrect_cmdline (Some ("Invalid browser name: " ^ !browser_name)))
        in
        (target_url, hub_url, caps, xml_out)
    with
        Incorrect_cmdline arg ->
            (match arg with | None -> () | Some msg -> print_endline msg) ;
            print_synopsis () ;
            exit 48
;; (* End of relevant code *)

Experimenting I could comment out the original author's switch for --browser-name and assign a single value by putting a solemn line surrounded by curly braces:

and caps=
    { caps_default with browserName = Chrome } (* Hard-coding a Chrome choice *)

or

and caps=
    { caps_default with username = !user_name } (* Sharing only --username *)

or

and caps=
    { caps_default with accessKey = !access_key } (* Sharing only --accessKey *)

Can someone point me in the correct direction? I want to keep the switch the original author wrote but also transmit the username and accessKey strings to the caps record on top of that. Can I change the caps object later? Can I modify default_caps beforehand and then copy it to caps?

This code is only run once per execution, so modifying the default_caps object is of absolutely no concern, even if it's not very kosher to do so.

Thanks for any assistance!

Update

Numerous thanks to Dr. Scofield's assistance so far. Curiously I am now being met with a slightly different error complaining about what I can only assume is a mis-match of type. Here is the content of the error:

File "tester.ml", line 110, characters 40-47:
Error: This expression has type Selenium.Json_client.capabilities
       but an expression was expected of type Selenium.Json_client.browser

Line 110 is

{ caps_default with browserName = browser;

in Mr. Scofield's code snippet, with 40-47 being browser.

The definition of default_caps's type capabilities is as such:

type capabilities =
  {
    browserName       : browser  ;
    version           : string   ;
    platform          : platform ;
    javascriptEnabled : bool     ;
    username          : string   ;
    accessKey         : string   ;
  }

And similarly, the type browser:

type browser =
  Chrome | Firefox | HtmlUnit | InternetExplorer | IPhone

The mli (interface, correct?) definitions are identical if that is important.

Update part 2

Minor correction only needed to simplify the code from this:

match !browser_name with
| "firefox"   -> { caps_default with browserName = Firefox }
| "chrome"    -> { caps_default with browserName = Chrome }
| "html_unit" -> { caps_default with browserName = HtmlUnit }
| "ie"        -> { caps_default with browserName = InternetExplorer }
| "iphone"    -> { caps_default with browserName = IPhone }
|  _        -> raise (Incorrect_cmdline (Some ("Invalid browser name: " ^ !browser_name)))

to this:

match !browser_name with
| "firefox"   -> Firefox
| "chrome"    -> Chrome
| "html_unit" -> HtmlUnit
| "ie"        -> InternetExplorer
| "iphone"    -> IPhone
|  _        -> raise (Incorrect_cmdline (Some ("Invalid browser name: " ^ !browser_name)))

Before continuing with:

in
{ caps_default with browserName = browser;
                    username    = !user_name;
                    accessKey   = !access_key;
}

Solution

  • You might have to learn a little more OCaml than you want to to do this :-)

    The thing named caps_default is a record, i.e., it has a record type. You're going to have to find the definition of this record type and add your new capabilities to it.

    Update

    OK, you already had the record type fixed up. Here's some code

        and caps =
            let browser = 
                match !browser_name with
                | "firefox"   ->  Firefox
                | "chrome"    ->  Chrome
                | "html_unit" ->  HtmlUnit
                | "ie"        ->  InternetExplorer
                | "iphone"    ->  IPhone
                |  _        ->
                    raise (Incorrect_cmdline
                       (Some ("Invalid browser name: " ^ !browser_name)))
            in
            { caps_default with browserName = browser;
                                username = !user_name;
                                accessKey = !access_key;
            }
        in
        (target_url, hub_url, caps, xml_out)
    

    (Needless to say, this is untested code.)

    Update 2

    (Fixed spelling of user_name, thanks.)