-
Notifications
You must be signed in to change notification settings - Fork 85
Add Process.Env #930
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 Process.Env #930
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,100 @@ type 'tag mgr_ty = [ `Process_mgr | `Platform of 'tag ] | |
| type 'a mgr = 'a r | ||
| constraint 'a = [> [> `Generic] mgr_ty] | ||
|
|
||
| module Env = struct | ||
| let on_windows = (Sys.os_type = "Win32") | ||
|
|
||
| module Name = struct | ||
| type t = string | ||
|
|
||
| let normalise = | ||
| if on_windows then String.uppercase_ascii | ||
| else Fun.id | ||
|
|
||
| let compare x y = | ||
| String.compare (normalise x) (normalise y) | ||
|
|
||
| let starts_with ~prefix = | ||
| let prefix = normalise prefix in | ||
| fun x -> String.starts_with ~prefix (normalise x) | ||
|
|
||
| let validate t = | ||
| let bad_char = function | ||
| | '\000' | '=' -> true | ||
| | _ -> false | ||
| in | ||
| if t = "" || String.exists bad_char t then | ||
| Fmt.invalid_arg "Invalid environment variable name %S" t | ||
| end | ||
|
|
||
| module M = Map.Make(Name) | ||
|
|
||
| type t = string array | ||
|
|
||
| let of_array = Fun.id | ||
| let to_array = Fun.id | ||
| let empty = [| |] | ||
|
|
||
| let validate_value value = | ||
| if String.contains value '\000' then | ||
| Fmt.invalid_arg "Invalid environment variable value %S" value | ||
|
|
||
| let validate_binding (name, value) = | ||
| Name.validate name; | ||
| Option.iter validate_value value | ||
|
|
||
| let entry name value = | ||
| Printf.sprintf "%s=%s" name value | ||
|
|
||
| let get_opt name t = | ||
| Name.validate name; | ||
| let prefix = name ^ "=" in | ||
| Array.find_opt (Name.starts_with ~prefix) t | ||
| |> Option.map (fun e -> | ||
| let i = String.length prefix in | ||
| String.sub e i (String.length e - i) | ||
| ) | ||
|
|
||
| let override bindings t = | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a deviation from execve behaviour, which passes through duplicate bindings and lets glibc/musl/etc handle that. Not necessarily a bad thing though, I haven't investigated what the libcs do yet.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add some docs, but the idea is that the raw
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes sense. What about case sensitivity? I think windows end keys are insensitive
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. I guess the easiest way would be to change the behaviour of
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've pushed a commit that does case insensitive compares on Windows now. It's not very efficient, but there aren't usually many variables anyway.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The change looks good, but I really dislike the portable interface varying behaviour based on which host it's running on. Here's a radical idea: why not specify our Eio environment interface as explicitly being case insensitive? We are already constraining it to forbid duplicates, and it seems like normalising on case should also be very safe. We could also preserve the case at the Eio level (so it's passed through as-is) but is case-insensitive for comparisons.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Being case insensitive in general seems bad: it's a surprising change to the expected behaviour, and it causes trouble with non-ascii encodings. The other option is to track whether a particular environment is Windows-style or not. But we can't use a flag at the moment because the type needs to be A simpler solution is to recommend that environment variable names are upper-case (which they mostly are anyway). As long as all variables are uppercase, the Windows and POSIX behaviours are the same anyway.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
agreed! good idea |
||
| List.iter validate_binding bindings; | ||
| let all_bindings = M.of_list bindings in | ||
| let bindings = ref all_bindings in | ||
| let updated = | ||
| Array.to_list t | ||
| |> List.filter_map (fun e -> | ||
| match String.index e '=' with | ||
| | exception Not_found -> Some e (* Not a normal k=v entry *) | ||
| | i -> | ||
| let name = String.sub e 0 i in | ||
| match M.find_opt name all_bindings with | ||
| | None -> Some e (* We're not changing this *) | ||
| | Some x -> | ||
| if M.mem name !bindings then ( | ||
| bindings := M.remove name !bindings; | ||
| match x with | ||
| | None -> None (* Remove existing entry *) | ||
| | Some v -> Some (entry name v) (* Update existing entry *) | ||
| ) else None (* Remove duplicate entry *) | ||
| ) | ||
| in | ||
| let extra = | ||
| M.to_list !bindings | ||
| |> List.filter_map (function | ||
| | _, None -> None (* Remove entry that wasn't there anyway *) | ||
| | k, Some v -> | ||
| Some (entry k v) (* Add new entry *) | ||
| ) | ||
| in | ||
| Array.of_list (updated @ extra) | ||
|
|
||
| let of_bindings xs = | ||
| override (List.map (fun (k, v) -> (k, Some v)) xs) empty | ||
|
|
||
| let pp f t = | ||
| Fmt.pf f "[@[<v>%a@]]" | ||
| (Fmt.array ~sep:Fmt.cut (Fmt.fmt "%S")) t | ||
| end | ||
|
|
||
| module Pi = struct | ||
| module type PROCESS = sig | ||
| type t | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.