 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Predicates for reasoning about the operating system (OS) environment.
   Written July 2020 by Markus Triska (triska@metalevel.at).

   Lists of characters are used throughout to represent keys and values.

   Example:

       ?- getenv("LANG", Ls).
          Ls = "en_US.UTF-8".

   Public domain code.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/** Predicates for reasoning about the operating system (OS) environment.

This includes predicates about environment variables, calls to shell and
finding out the PID of the running system.
*/

:- module(os, [getenv/2,
               setenv/2,
               unsetenv/1,
               shell/1,
               shell/2,
               pid/1,
	       raw_argv/1,
	       argv/1]).

:- use_module(library(error)).
:- use_module(library(charsio)).
:- use_module(library(lists)).
:- use_module(library(si)).

%% getenv(+Key, -Value).
%
% True iff Value contains the value of the environment variable Key.
% Example:
%
% ```
% ?- getenv("LANG", Ls).
%    Ls = "en_US.UTF-8".
% ```
getenv(Key, Value) :-
        must_be_env_var(Key),
        '$getenv'(Key, Value).

%% setenv(+Key, +Value).
%
% Sets the environment variable Key to Value
setenv(Key, Value) :-
        must_be_env_var(Key),
        must_be_chars(Value),
        '$setenv'(Key, Value).

%% unsetenv(+Key).
%
% Unsets the environment variable Key
unsetenv(Key) :-
        must_be_env_var(Key),
        '$unsetenv'(Key).

%% shell(+Command)
%
% Equivalent to `shell(Command, 0)`.
shell(Command) :- shell(Command, 0).

%% shell(+Command, -Status).
%
% True iff executes Command in a shell of the operating system and the exit code is Status.
% Keep in mind the shell syntax is dependant on the operating system, so it should be
% used very carefully.
%
% Example (using Linux and fish shell):
%
% ```
% ?- shell("echo $SHELL", Status).
% /bin/fish
%    Status = 0.
% ```
shell(Command, Status) :-
    must_be_chars(Command),
    can_be(integer, Status),
    '$shell'(Command, Status).

%% pid(-PID).
%
% True iff PID is the process identification number of current Scryer Prolog instance.
pid(PID) :-
        can_be(integer, PID),
        '$pid'(PID).

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   For now, we only support a restricted subset of variable names.

   The reason is that Rust may panic if a key is empty, contains an
   ASCII equals sign '=' or the NUL character '\0', or when the value
   contains the NUL character.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

must_be_env_var(Cs) :-
        must_be_chars(Cs),
        Cs = [_|_],
        (   maplist(permitted, Cs) -> true
        ;   domain_error(env_var, Cs, os)
        ).

permitted(C) :- char_type(C, alnum).
permitted(C) :- char_type(C, ascii_punctuation).
permitted('_').

must_be_chars(Cs) :-
        must_be(list, Cs),
        maplist(must_be(character), Cs).

%% raw_argv(-Argv)
%
% True iff Argv is the list of arguments that this program was started with (usually passed via command line).
% In contrast to `argv/1`, this version includes every argument, without any postprocessing, just as the operating
% system reports it to the system. This includes-flags of Scryer itself, which are not needed in general.
raw_argv(Argv) :-
    can_be(list, Argv),
    '$argv'(Argv).

%% argv(-Argv)
%
% True if Argv is the list of arguments that this program was started with (usually passed via command line).
% In this version, only arguments specific to the program are passed. To differentiate between the system
% arguments and the program arguments, we use `--` as a separator.
%
% Example:
%
% ```
% % Call with scryer-prolog -f -- -t hello
% ?- argv(X).
%     X = ["-t", "hello"].
% ```
argv(Argv) :-
    can_be(list, Argv),
    '$argv'(Argv0),
    ( append(_, ["--"|Argv1], Argv0) ->
        Argv = Argv1
    ;
        Argv = []
    ).
