Skip to content
This repository

New functions for the System module #230

Merged
merged 13 commits into from over 2 years ago

4 participants

Alexei Sholik José Valim EricHripko Yurii Rashkovskii
Alexei Sholik
alco commented

Not sure how to deal with the "binary vs charlist" issue. I tried to reach a balance with adding binary support for some functions. put_env would require 4 clauses to accept all pairs "binary-list", "list-binary", "binary-binary", and "list-list", so I didn't do it for this function.

lib/system.ex
... ... @@ -65,6 +65,69 @@ defmodule System do
65 65 end
66 66
67 67 @doc """
  68 + Executes `command` in a command shell of the target OS, captures the standard
  69 + output of the command and returns this result as a string.
  70 +
  71 + `command` can be an atom, a charlist or a binary.
  72 + """
  73 + def cmd(command) when is_binary(command) do
  74 + cmd binary_to_list(command)
1
José Valim Owner
josevalim added a note

We should go with Erlang philosophy here. If the argument is a binary, we return a binary. If a char list, we return a char list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
José Valim
Owner

I see your point about binary and char list. In general, Erlang accepts both arguments and the result is given according to the argument (if a binary is given, we return a binary). However, supporting both variants in many functions will become rather annoying.

Alexei Sholik
alco commented

I'm inclined to revert to simply allowing charlists for the functions in this PR, like Erlang does. When people start asking for binaries, we can always review the decision.

José Valim
Owner

I disagree because Elixir actually favors binaries over char lists. So we could make it otherwise, to not accept char lists, but for sure we need to accept binaries (the Erlang community is slowing catching up in adding binaries to its libraries but it is becoming more and more frequent, for example, Cowboy and Riak are very binary driven).

Alexei Sholik
alco commented

Ok, I don't have enough familiarity with the issue, so I'll go with your argument. Should I add the binary variants of put_env/2 in this PR?

José Valim
Owner

We can just go with def put_env(key, value), do: :os.putenv(to_char_list(key), to_char_list(value))

Alexei Sholik
alco commented

I've added a few more updates.

Alexei Sholik
alco commented

I have updated git version and date queries and rebased on master. Can we get someone to test this on windows? /cc @yksirius

Simply pull my branch over at https://github.com/alco/elixir/tree/system-module and try to build it. Then check the result with

bin/elixir.bat -e "IO.inspect System.build_info"

or how it's usually done on Windows...

EricHripko

Hello!

Here is a result:

e:__AlcoTest\elixir\bin>elixir.bat -e IO.inspect(System.build_info)
{"0.9.0.dev","16f95060c75ee92659d7f0439d81f6b8c8871c8d","Thu, 05 Apr 2012 14:13:
36 GMT"}

Alexei Sholik
alco commented

Great! Thanks.

I think it's ready to be merged.

José Valim
Owner

Great, thanks a lot. Finally, can we have tests where possible? (at least get_env, put_env and cmd)

Alexei Sholik
alco commented

ping

José Valim josevalim merged commit 58d45c1 into from
Yurii Rashkovskii

How about using simple :os.cmd/1 here?

os.cmd returns an error as its output if git is not installed on the system. You can see that I had used it and then switched to a port.

didn't see that, sorry — was just skimming through. but I'd actually do os:cmd anyway but just do os:find_executable before that. How about that?

Sounds nice, that would be a lot simpler than ports. What bothers me is whether this functionality is useful at all. For instance, when installing via homebrew, the commit sha-1 is not embedded within the executable.

That's a good point, too. But if we are to keep this code, I'd strongly recommend switching to os:find_executable/1 and os:cmd/1. That would just trim down the code tremendously.

I agree. Contributions are welcome ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.

Showing 2 changed files with 158 additions and 7 deletions. Show diff stats Hide diff stats

  1. +109 7 lib/system.ex
  2. +49 0 test/elixir/system_test.exs
116 lib/system.ex
@@ -7,9 +7,12 @@ defmodule System.GitCompiler do
7 7 defmacro generate do
8 8 quote do
9 9 @doc """
10   - Returns a tuple { Elixir version, commit sha-1, build date }
  10 + Returns a tuple { Elixir version, commit sha-1, build date }.
  11 +
  12 + The format of the return value may change in a future release. Please
  13 + make sure your code doesn't depend on it.
11 14 """
12   - def build_version do
  15 + def build_info do
13 16 { System.version,
14 17 unquote(get_head_sha),
15 18 unquote(get_date) }
@@ -17,16 +20,45 @@ defmodule System.GitCompiler do
17 20 end
18 21 end
19 22
  23 + @doc """
  24 + Tries to run `git rev-parse HEAD`. In case of success returns the
  25 + commit sha, otherwise returns an empty string.
  26 + """
20 27 defp get_head_sha do
21   - normalize :os.cmd 'git rev-parse HEAD'
  28 + # The following failures are possible:
  29 + #
  30 + # 1) there is no `git` command
  31 + # 2) pwd is not a git repository
  32 + #
  33 + command = 'git rev-parse HEAD'
  34 + opts = [:stream, :exit_status, :use_stdio,
  35 + :stderr_to_stdout, :in, :eof]
  36 + port = :erlang.open_port {:spawn, command}, opts
  37 +
  38 + output = read_port port
  39 + case output do
  40 + match: { 0, data }
  41 + Regex.replace_all %r/\n/, to_binary(data), ""
  42 + else:
  43 + ""
  44 + end
22 45 end
23 46
24   - defp get_date do
25   - normalize :os.cmd 'TZ=GMT date'
  47 + defp read_port(port, data // []) do
  48 + receive do
  49 + match: {^port, {:data, new_data}}
  50 + read_port port, [new_data|data]
  51 + match: {^port, :eof}
  52 + :erlang.port_close port
  53 + receive do
  54 + match: {^port, {:exit_status, exit_status}}
  55 + {exit_status, List.reverse data}
  56 + end
  57 + end
26 58 end
27 59
28   - defp normalize(string) do
29   - Regex.replace_all %r(\n), to_binary(string), ""
  60 + defp get_date do
  61 + list_to_binary :httpd_util.rfc1123_date
30 62 end
31 63 end
32 64
@@ -65,6 +97,76 @@ defmodule System do
65 97 end
66 98
67 99 @doc """
  100 + Executes `command` in a command shell of the target OS, captures the standard
  101 + output of the command and returns this result.
  102 +
  103 + `command` can a charlist or a binary.
  104 + """
  105 + def cmd(command) when is_binary(command) do
  106 + list_to_binary cmd(binary_to_list(command))
  107 + end
  108 +
  109 + def cmd(command), do: :os.cmd command
  110 +
  111 + @doc """
  112 + Returns a list of all environment variables. Each environment variable is
  113 + given as a single string of the format "VarName=Value", where VarName is the
  114 + name of the variable and Value its value.
  115 + """
  116 + def get_env do
  117 + Enum.map :os.getenv, list_to_binary &1
  118 + end
  119 +
  120 + @doc """
  121 + Returns the Value of the environment variable `varname`, or nil if the
  122 + environment variable is undefined.
  123 + """
  124 + def get_env(varname) when is_binary(varname) do
  125 + list_to_binary get_env(binary_to_list(varname))
  126 + end
  127 +
  128 + def get_env(varname) do
  129 + case :os.getenv(varname) do
  130 + match: false
  131 + nil
  132 + match: other
  133 + other
  134 + end
  135 + end
  136 +
  137 + @doc """
  138 + Returns the process identifier of the current Erlang emulator in the format
  139 + most commonly used by the operating system environment.
  140 +
  141 + See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
  142 + """
  143 + def get_pid, do: list_to_binary(:os.getpid)
  144 +
  145 + @doc """
  146 + Sets a new `value` for the environment variable `varname`.
  147 + """
  148 + def put_env(varname, value) when is_list(varname) and is_list(value) do
  149 + :os.putenv varname, value
  150 + end
  151 +
  152 + def put_env(varname, value) do
  153 + :os.putenv to_char_list(varname), to_char_list(value)
  154 + end
  155 +
  156 + @doc """
  157 + Sets a new value for each environment variable corresponding to each key in
  158 + `dict`.
  159 + """
  160 + def put_env(dict) do
  161 + Enum.each dict, fn({key, val}) -> put_env key, val end
  162 + end
  163 +
  164 + @doc """
  165 + Returns the current working directory as a binary.
  166 + """
  167 + def pwd, do: :filename.absname("")
  168 +
  169 + @doc """
68 170 Get the stacktrace.
69 171 """
70 172 def stacktrace do
49 test/elixir/system_test.exs
... ... @@ -0,0 +1,49 @@
  1 +Code.require_file "../test_helper", __FILE__
  2 +
  3 +require Erlang.os, as: OS
  4 +
  5 +defmodule SystemTest do
  6 + use ExUnit.Case
  7 +
  8 + test :argv do
  9 + list = OS.cmd('bin/elixir -e "IO.inspect System.argv" sample_script.exs -o opt arg1 arg2 --long-opt 10')
  10 + {args, _} = Code.eval list, []
  11 + expected = ["-o", "opt", "arg1", "arg2", "--long-opt", "10"]
  12 + assert_equal expected, args
  13 + end
  14 +
  15 + test :at_exit do
  16 + output = OS.cmd('bin/elixir -e "System.at_exit(fn(x) -> IO.inspect x end)"')
  17 + assert_equal '0\n', output
  18 + end
  19 +
  20 + test :cmd do
  21 + assert is_list(System.cmd 'list')
  22 + assert is_binary(System.cmd "binary")
  23 + end
  24 +
  25 + test :get_env do
  26 + list_cmd = %c{SECRET_VAR=elixir bin/elixir -e "IO.inspect System.get_env('SECRET_VAR')"}
  27 + {output, _} = Code.eval OS.cmd(list_cmd), []
  28 + assert_equal 'elixir', output
  29 +
  30 + bin_cmd = %c{SECRET_VAR=elixir bin/elixir -e 'IO.inspect System.get_env("SECRET_VAR")'}
  31 + {output, _} = Code.eval OS.cmd(bin_cmd), []
  32 + assert_equal "elixir", output
  33 +
  34 + assert Enum.all? System.get_env, is_binary &1
  35 + end
  36 +
  37 + test :put_env do
  38 + assert_equal nil, System.get_env('SECRET_VAR')
  39 +
  40 + output = OS.cmd(%c{bin/elixir -e "System.put_env('SECRET_VAR', 'elixir'); IO.inspect System.get_env('SECRET_VAR')"})
  41 + {varvalue, _} = Code.eval output, []
  42 + assert_equal 'elixir', varvalue
  43 + end
  44 +
  45 + test :pwd do
  46 + assert is_binary(System.pwd)
  47 + assert_equal Regex.replace_all(%r/\n/, OS.cmd('pwd'), ""), binary_to_list(System.pwd)
  48 + end
  49 +end

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.