Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

New functions for the System module #230

Merged
merged 13 commits into from

4 participants

@alco
Owner

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
end
@doc """
+ Executes `command` in a command shell of the target OS, captures the standard
+ output of the command and returns this result as a string.
+
+ `command` can be an atom, a charlist or a binary.
+ """
+ def cmd(command) when is_binary(command) do
+ cmd binary_to_list(command)
@josevalim Owner

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
@josevalim
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.

@alco
Owner

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.

@josevalim
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).

@alco
Owner

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?

@josevalim
Owner

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

@alco
Owner

I've added a few more updates.

@alco
Owner

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"}

@alco
Owner

Great! Thanks.

I think it's ready to be merged.

@josevalim
Owner

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

@alco
Owner

ping

@josevalim josevalim merged commit 58d45c1 into from
@yrashk

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

Owner

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?

Owner

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.

Owner

I agree. Contributions are welcome ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 5, 2012
  1. @alco
  2. @alco

    Add useful functions to System

    alco authored
  3. @alco

    Add binary counterparts of cmd and get_env

    alco authored
    I was also considering adding binary support to put_env, but it would bloat
    the code in my opinion. Don't know what's our general policy for using binary vs
    charlist
  4. @alco

    Add an experimental put_env(dict)

    alco authored
    This might not work at the moment, but the API does make sense for me
  5. @alco

    Fix enumeration in put_env(dict)

    alco authored
  6. @alco
  7. @alco

    Add binary support for put_env/2

    alco authored
  8. @alco
  9. @alco
  10. @alco
  11. @alco
  12. @alco

    Minor code cleanup

    alco authored
  13. @alco

    Add tests for System

    alco authored
This page is out of date. Refresh to see the latest.
Showing with 158 additions and 7 deletions.
  1. +109 −7 lib/system.ex
  2. +49 −0 test/elixir/system_test.exs
View
116 lib/system.ex
@@ -7,9 +7,12 @@ defmodule System.GitCompiler do
defmacro generate do
quote do
@doc """
- Returns a tuple { Elixir version, commit sha-1, build date }
+ Returns a tuple { Elixir version, commit sha-1, build date }.
+
+ The format of the return value may change in a future release. Please
+ make sure your code doesn't depend on it.
"""
- def build_version do
+ def build_info do
{ System.version,
unquote(get_head_sha),
unquote(get_date) }
@@ -17,16 +20,45 @@ defmodule System.GitCompiler do
end
end
+ @doc """
+ Tries to run `git rev-parse HEAD`. In case of success returns the
+ commit sha, otherwise returns an empty string.
+ """
defp get_head_sha do
- normalize :os.cmd 'git rev-parse HEAD'
+ # The following failures are possible:
+ #
+ # 1) there is no `git` command
+ # 2) pwd is not a git repository
+ #
+ command = 'git rev-parse HEAD'
+ opts = [:stream, :exit_status, :use_stdio,
+ :stderr_to_stdout, :in, :eof]
+ port = :erlang.open_port {:spawn, command}, opts
+
+ output = read_port port
+ case output do
+ match: { 0, data }
+ Regex.replace_all %r/\n/, to_binary(data), ""
+ else:
+ ""
+ end
end
- defp get_date do
- normalize :os.cmd 'TZ=GMT date'
+ defp read_port(port, data // []) do
+ receive do
+ match: {^port, {:data, new_data}}
+ read_port port, [new_data|data]
+ match: {^port, :eof}
+ :erlang.port_close port
+ receive do
+ match: {^port, {:exit_status, exit_status}}
+ {exit_status, List.reverse data}
+ end
+ end
end
- defp normalize(string) do
- Regex.replace_all %r(\n), to_binary(string), ""
+ defp get_date do
+ list_to_binary :httpd_util.rfc1123_date
end
end
@@ -65,6 +97,76 @@ defmodule System do
end
@doc """
+ Executes `command` in a command shell of the target OS, captures the standard
+ output of the command and returns this result.
+
+ `command` can a charlist or a binary.
+ """
+ def cmd(command) when is_binary(command) do
+ list_to_binary cmd(binary_to_list(command))
+ end
+
+ def cmd(command), do: :os.cmd command
+
+ @doc """
+ Returns a list of all environment variables. Each environment variable is
+ given as a single string of the format "VarName=Value", where VarName is the
+ name of the variable and Value its value.
+ """
+ def get_env do
+ Enum.map :os.getenv, list_to_binary &1
+ end
+
+ @doc """
+ Returns the Value of the environment variable `varname`, or nil if the
+ environment variable is undefined.
+ """
+ def get_env(varname) when is_binary(varname) do
+ list_to_binary get_env(binary_to_list(varname))
+ end
+
+ def get_env(varname) do
+ case :os.getenv(varname) do
+ match: false
+ nil
+ match: other
+ other
+ end
+ end
+
+ @doc """
+ Returns the process identifier of the current Erlang emulator in the format
+ most commonly used by the operating system environment.
+
+ See http://www.erlang.org/doc/man/os.html#getpid-0 for more info.
+ """
+ def get_pid, do: list_to_binary(:os.getpid)
+
+ @doc """
+ Sets a new `value` for the environment variable `varname`.
+ """
+ def put_env(varname, value) when is_list(varname) and is_list(value) do
+ :os.putenv varname, value
+ end
+
+ def put_env(varname, value) do
+ :os.putenv to_char_list(varname), to_char_list(value)
+ end
+
+ @doc """
+ Sets a new value for each environment variable corresponding to each key in
+ `dict`.
+ """
+ def put_env(dict) do
+ Enum.each dict, fn({key, val}) -> put_env key, val end
+ end
+
+ @doc """
+ Returns the current working directory as a binary.
+ """
+ def pwd, do: :filename.absname("")
+
+ @doc """
Get the stacktrace.
"""
def stacktrace do
View
49 test/elixir/system_test.exs
@@ -0,0 +1,49 @@
+Code.require_file "../test_helper", __FILE__
+
+require Erlang.os, as: OS
+
+defmodule SystemTest do
+ use ExUnit.Case
+
+ test :argv do
+ list = OS.cmd('bin/elixir -e "IO.inspect System.argv" sample_script.exs -o opt arg1 arg2 --long-opt 10')
+ {args, _} = Code.eval list, []
+ expected = ["-o", "opt", "arg1", "arg2", "--long-opt", "10"]
+ assert_equal expected, args
+ end
+
+ test :at_exit do
+ output = OS.cmd('bin/elixir -e "System.at_exit(fn(x) -> IO.inspect x end)"')
+ assert_equal '0\n', output
+ end
+
+ test :cmd do
+ assert is_list(System.cmd 'list')
+ assert is_binary(System.cmd "binary")
+ end
+
+ test :get_env do
+ list_cmd = %c{SECRET_VAR=elixir bin/elixir -e "IO.inspect System.get_env('SECRET_VAR')"}
+ {output, _} = Code.eval OS.cmd(list_cmd), []
+ assert_equal 'elixir', output
+
+ bin_cmd = %c{SECRET_VAR=elixir bin/elixir -e 'IO.inspect System.get_env("SECRET_VAR")'}
+ {output, _} = Code.eval OS.cmd(bin_cmd), []
+ assert_equal "elixir", output
+
+ assert Enum.all? System.get_env, is_binary &1
+ end
+
+ test :put_env do
+ assert_equal nil, System.get_env('SECRET_VAR')
+
+ output = OS.cmd(%c{bin/elixir -e "System.put_env('SECRET_VAR', 'elixir'); IO.inspect System.get_env('SECRET_VAR')"})
+ {varvalue, _} = Code.eval output, []
+ assert_equal 'elixir', varvalue
+ end
+
+ test :pwd do
+ assert is_binary(System.pwd)
+ assert_equal Regex.replace_all(%r/\n/, OS.cmd('pwd'), ""), binary_to_list(System.pwd)
+ end
+end
Something went wrong with that request. Please try again.