-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Extract platform-specifics from ENV to Crystal::System::Env and implement for win32 #6333
Changes from 6 commits
05e9b91
be3115e
9bf5cb8
d2311e1
75715f7
7adb2cc
af0e517
7f6d3c1
ca1967b
0b33619
ce98927
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 |
---|---|---|
@@ -0,0 +1,19 @@ | ||
module Crystal::System::Env | ||
# Sets an environment variable or unsets it if *value* is `nil`. | ||
# def self.set(key : String, value : String?) : Nil | ||
|
||
# Gets an environment variable. | ||
# def self.get(key : String) : String? | ||
|
||
# Returns `true` if environment variable is set. | ||
# def self.has_key?(key : String) : Bool | ||
|
||
# Iterates all environment variables. | ||
# def self.each(&block : String, String ->) | ||
end | ||
|
||
{% if flag?(:win32) %} | ||
require "./win32/env" | ||
{% else %} | ||
require "./unix/env" | ||
{% end %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
require "c/stdlib" | ||
|
||
module Crystal::System::Env | ||
# Sets an environment variable. | ||
def self.set(key : String, value : String) : Nil | ||
key.check_no_null_byte("key") | ||
value.check_no_null_byte("value") | ||
|
||
if LibC.setenv(key, value, 1) != 0 | ||
raise Errno.new("setenv") | ||
end | ||
end | ||
|
||
# Unsets an environment variable. | ||
def self.set(key : String, value : Nil) : Nil | ||
key.check_no_null_byte("key") | ||
|
||
if LibC.unsetenv(key) != 0 | ||
raise Errno.new("unsetenv") | ||
end | ||
end | ||
|
||
# Gets an environment variable. | ||
def self.get(key : String) : String? | ||
key.check_no_null_byte("key") | ||
|
||
if value = LibC.getenv(key) | ||
String.new(value) | ||
end | ||
end | ||
|
||
# Returns `true` if environment variable is set. | ||
def self.has_key?(key : String) : Bool | ||
key.check_no_null_byte("key") | ||
|
||
!!LibC.getenv(key) | ||
end | ||
|
||
# Iterates all environment variables. | ||
def self.each(&block : String, String ->) | ||
environ_ptr = LibC.environ | ||
while environ_ptr | ||
environ_value = environ_ptr.value | ||
if environ_value | ||
key_value = String.new(environ_value).split('=', 2) | ||
key = key_value[0] | ||
value = key_value[1]? || "" | ||
yield key, value | ||
environ_ptr += 1 | ||
else | ||
break | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
require "crystal/system/windows" | ||
require "c/winbase" | ||
|
||
module Crystal::System::Env | ||
# Sets an environment variable or unsets it if *value* is `nil`. | ||
def self.set(key : String, value : String) : Nil | ||
key.check_no_null_byte("key") | ||
value.check_no_null_byte("value") | ||
|
||
if LibC.SetEnvironmentVariableW(key.to_utf16, value.to_utf16) == 0 | ||
raise WinError.new("SetEnvironmentVariableW") | ||
end | ||
end | ||
|
||
# Unsets an environment variable. | ||
def self.set(key : String, value : Nil) : Nil | ||
key.check_no_null_byte("key") | ||
|
||
if LibC.SetEnvironmentVariableW(key.to_utf16, nil) == 0 | ||
raise WinError.new("SetEnvironmentVariableW") | ||
end | ||
end | ||
|
||
# Gets an environment variable. | ||
def self.get(key : String) : String? | ||
key.check_no_null_byte("key") | ||
|
||
System.retry_wstr_buffer do |buffer, small_buf| | ||
length = LibC.GetEnvironmentVariableW(key.to_utf16, buffer, buffer.size) | ||
if 0 < length < buffer.size | ||
return String.from_utf16(buffer[0, length]) | ||
elsif small_buf && length > 0 | ||
next length | ||
elsif length == 0 && LibC.GetLastError == WinError::ERROR_ENVVAR_NOT_FOUND | ||
return | ||
else | ||
raise WinError.new("GetEnvironmentVariableW") | ||
end | ||
end | ||
end | ||
|
||
# Returns `true` if environment variable is set. | ||
def self.has_key?(key : String) : Bool | ||
key.check_no_null_byte("key") | ||
|
||
buffer = uninitialized UInt16[1] | ||
LibC.GetEnvironmentVariableW(key.to_utf16, buffer, buffer.size) != 0 | ||
end | ||
|
||
# Iterates all environment variables. | ||
def self.each(&block : String, String ->) | ||
orig_pointer = pointer = LibC.GetEnvironmentStringsW | ||
raise WinError.new("GetEnvironmentStringsW") if pointer.null? | ||
|
||
begin | ||
while !pointer.value.zero? | ||
string, pointer = String.from_utf16(pointer) | ||
key_value = string.split('=', 2) | ||
key = key_value[0] | ||
value = key_value[1]? || "" | ||
yield key, value | ||
end | ||
ensure | ||
LibC.FreeEnvironmentStringsW(orig_pointer) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4244,8 +4244,11 @@ class String | |
# Raises an `ArgumentError` if `self` has null bytes. Returns `self` otherwise. | ||
# | ||
# This method should sometimes be called before passing a `String` to a C function. | ||
def check_no_null_byte | ||
raise ArgumentError.new("String contains null byte") if byte_index(0) | ||
def check_no_null_byte(name = nil) | ||
if byte_index(0) | ||
name = "`#{name}` " if name | ||
raise ArgumentError.new("String #{name}contains null byte") | ||
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. Why is this on two lines?? Also, I'd rather the entire exception message was passed in, i.e. 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 agree with the first comment, disagree with the second one. 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 only thing you save by doing it the other way is a bit of typing, and the benefit of having fully customized error messages. For "Key contains null byte" vs "String `key` contains null byte" - the former is way better. Please, let's keep it simple, understandable and flexible. 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. Two lines because I forgot a Maybe full message is better. But actually, I don't think there is much benefit from it. Yes, it's more flexible, but I don't see any use for that. |
||
end | ||
self | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the backticks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To show that this refers to a name. This formatting is used in many other compiler and stdlib error messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird, I never noticed that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not that common
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO
Key contains null byte
reads way better thanString 'key' contains null byte
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@asterite Do you mean
name
should be required?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps
check_no_null_byte(what = "String")
then just#{what} contains null byte
?Or just go with making it fully configurable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm looking at the code, it always seems to be a check against an argument. To avoid repeating the argument name, maybe we can have:
Then it can be used like this:
That way there's no string interpolation, and there's no need to repeat the argument name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is bikeshedding and overengineering. The diff is fine as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RX14 I was writing the same thing. All are valid options and it doesn't make a difference regarding ENV.
Anyone can send a PR later to refactor
check_no_null_byte
if you care enough.