Skip to content
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

Implement Path.home on Windows #11503

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ jobs:
cl /MT /c src\llvm\ext\llvm_ext.cc -I llvm\include /Fosrc\llvm\ext\llvm_ext.obj
- name: Link Crystal executable
run: |
Invoke-Expression "cl crystal.obj /Fecrystal-cross src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre.lib libs\gc.lib WS2_32.lib advapi32.lib libcmt.lib dbghelp.lib legacy_stdio_definitions.lib /link /LIBPATH:$(pwd)\libs /STACK:10000000"
Invoke-Expression "cl crystal.obj /Fecrystal-cross src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre.lib libs\gc.lib WS2_32.lib advapi32.lib libcmt.lib dbghelp.lib ole32.lib shell32.lib legacy_stdio_definitions.lib /link /LIBPATH:$(pwd)\libs /STACK:10000000"

- name: Re-build Crystal
run: |
Expand Down
105 changes: 9 additions & 96 deletions spec/std/file_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -558,102 +558,15 @@ describe "File" do
end
end

# TODO: these specs are redundant with path_spec.cr
describe "expand_path" do
pending_win32 "converts a pathname to an absolute pathname" do
File.expand_path("").should eq(base)
File.expand_path("a").should eq(File.join([base, "a"]))
File.expand_path("a", nil).should eq(File.join([base, "a"]))
end

pending_win32 "converts a pathname to an absolute pathname, Ruby-Talk:18512" do
File.expand_path(".a").should eq(File.join([base, ".a"]))
File.expand_path("..a").should eq(File.join([base, "..a"]))
File.expand_path("a../b").should eq(File.join([base, "a../b"]))
end

pending_win32 "keeps trailing dots on absolute pathname" do
File.expand_path("a.").should eq(File.join([base, "a."]))
File.expand_path("a..").should eq(File.join([base, "a.."]))
end

pending_win32 "converts a pathname to an absolute pathname, using a complete path" do
File.expand_path("", "#{tmpdir}").should eq("#{tmpdir}")
File.expand_path("a", "#{tmpdir}").should eq("#{tmpdir}/a")
File.expand_path("../a", "#{tmpdir}/xxx").should eq("#{tmpdir}/a")
File.expand_path(".", "#{rootdir}").should eq("#{rootdir}")
end

pending_win32 "expands a path with multi-byte characters" do
File.expand_path("Ångström").should eq("#{base}/Ångström")
end

pending_win32 "expands /./dir to /dir" do
File.expand_path("/./dir").should eq("/dir")
end

pending_win32 "replaces multiple / with a single /" do
File.expand_path("////some/path").should eq("/some/path")
File.expand_path("/some////path").should eq("/some/path")
end

pending_win32 "expand path with" do
File.expand_path("../../bin", "/tmp/x").should eq("/bin")
File.expand_path("../../bin", "/tmp").should eq("/bin")
File.expand_path("../../bin", "/").should eq("/bin")
File.expand_path("../bin", "tmp/x").should eq(File.join([base, "tmp", "bin"]))
File.expand_path("../bin", "x/../tmp").should eq(File.join([base, "bin"]))
end

pending_win32 "expand_path for common unix path gives a full path" do
File.expand_path("/tmp/").should eq("/tmp/")
File.expand_path("/tmp/../../../tmp").should eq("/tmp")
File.expand_path("").should eq(base)
File.expand_path("./////").should eq(File.join(base, ""))
File.expand_path(".").should eq(base)
File.expand_path(base).should eq(base)
end

pending_win32 "converts a pathname to an absolute pathname, using ~ (home) as base" do
File.expand_path("~/", home: true).should eq(File.join(home, ""))
File.expand_path("~/..badfilename", home: true).should eq(File.join(home, "..badfilename"))
File.expand_path("..", home: true).should eq("/#{base.split('/')[0...-1].join('/')}".gsub(%r{\A//}, "/"))
File.expand_path("~/a", "~/b", home: true).should eq(File.join(home, "a"))
File.expand_path("~", home: true).should eq(home)
File.expand_path("~", "/tmp/gumby/ddd", home: true).should eq(home)
File.expand_path("~/a", "/tmp/gumby/ddd", home: true).should eq(File.join([home, "a"]))
end

pending_win32 "converts a pathname to an absolute pathname, using ~ (home) as base (trailing /)" do
prev_home = home
begin
ENV["HOME"] = File.expand_path(datapath)
File.expand_path("~/", home: true).should eq(File.join(home, ""))
File.expand_path("~/..badfilename", home: true).should eq(File.join(home, "..badfilename"))
File.expand_path("..", home: true).should eq("/#{base.split('/')[0...-1].join('/')}".gsub(%r{\A//}, "/"))
File.expand_path("~/a", "~/b", home: true).should eq(File.join(home, "a"))
File.expand_path("~", home: true).should eq(home)
File.expand_path("~", "/tmp/gumby/ddd", home: true).should eq(home)
File.expand_path("~/a", "/tmp/gumby/ddd", home: true).should eq(File.join([home, "a"]))
ensure
ENV["HOME"] = prev_home
end
end

pending_win32 "converts a pathname to an absolute pathname, using ~ (home) as base (HOME=/)" do
prev_home = home
begin
ENV["HOME"] = "/"
File.expand_path("~/", home: true).should eq(home)
File.expand_path("~/..badfilename", home: true).should eq(File.join(home, "..badfilename"))
File.expand_path("..", home: true).should eq("/#{base.split('/')[0...-1].join('/')}".gsub(/\A\/\//, "/"))
File.expand_path("~/a", "~/b", home: true).should eq(File.join(home, "a"))
File.expand_path("~", home: true).should eq(home)
File.expand_path("~", "/tmp/gumby/ddd", home: true).should eq(home)
File.expand_path("~/a", "/tmp/gumby/ddd", home: true).should eq(File.join([home, "a"]))
ensure
ENV["HOME"] = prev_home
end
# There are more detailled specs for `Path#expand` in path_spec.cr
describe ".expand_path" do
HertzDevil marked this conversation as resolved.
Show resolved Hide resolved
it "converts a pathname to an absolute pathname" do
File.expand_path("a/b").should eq(Path.new("a/b").expand(Dir.current).to_s)
File.expand_path("a/b", "c/d").should eq(Path.new("a/b").expand("c/d").to_s)
File.expand_path("~/b", home: "c/d").should eq(Path.new("~/b").expand(Dir.current, home: "c/d").to_s)
File.expand_path("~/b", "c/d", home: false).should eq(Path.new("~/b").expand("c/d", home: false).to_s)

File.expand_path(Path.new("a/b")).should eq(Path.new("a/b").expand(Dir.current).to_s)
end
end

Expand Down
31 changes: 22 additions & 9 deletions spec/std/path_spec.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require "spec"
require "./spec_helper"
require "../support/env"

private HOME_ENV_KEY = {% if flag?(:win32) %} "USERPROFILE" {% else %} "HOME" {% end %}
private BASE_POSIX = "/default/base"
private BASE_WINDOWS = "\\default\\base"
private HOME_WINDOWS = "C:\\Users\\Crystal"
Expand All @@ -12,15 +14,9 @@ end

private def it_expands_path(path, posix, windows = posix, *, base = nil, env_home = nil, expand_base = false, home = false, file = __FILE__, line = __LINE__)
assert_paths(path, posix, windows, %((base: "#{base}")), file, line) do |path|
prev_home = ENV["HOME"]?

begin
ENV["HOME"] = env_home || (path.windows? ? HOME_WINDOWS : HOME_POSIX)

with_env({HOME_ENV_KEY => env_home || (path.windows? ? HOME_WINDOWS : HOME_POSIX)}) do
base_arg = base || (path.windows? ? BASE_WINDOWS : BASE_POSIX)
path.expand(base_arg.not_nil!, expand_base: !!expand_base, home: home)
ensure
ENV["HOME"] = prev_home
end
end
end
Expand Down Expand Up @@ -584,7 +580,7 @@ describe Path do
describe "converts a pathname to an absolute pathname" do
it_expands_path("", BASE_POSIX, BASE_WINDOWS)
it_expands_path("a", {BASE_POSIX, "a"}, {BASE_WINDOWS, "a"})
it_expands_path("a", {BASE_POSIX, "a"}, {BASE_WINDOWS, "a"})
it_expands_path("a", {BASE_POSIX, "a"}, {BASE_WINDOWS, "a"}, base: nil)
end

describe "converts a pathname to an absolute pathname, Ruby-Talk:18512" do
Expand Down Expand Up @@ -625,7 +621,7 @@ describe Path do
it_expands_path("/some////path", "/some/path", "\\some\\path")
end

describe "expand path with" do
describe "expand path with .." do
it_expands_path("../../bin", "/bin", "\\bin", base: "/tmp/x")
it_expands_path("../../bin", "/bin", "\\bin", base: "/tmp")
it_expands_path("../../bin", "/bin", "\\bin", base: "/")
Expand Down Expand Up @@ -889,4 +885,21 @@ describe Path do
assert_paths_raw("foo.txt./", "foo.txt.", &.stem)
assert_paths_raw("foo..txt/", "foo.", &.stem)
end

describe ".home" do
it "uses home from environment variable if set" do
with_env({HOME_ENV_KEY => "foo/bar"}) do
Path.home.should eq(Path.new("foo/bar"))
end
end

# TODO: check that this is the home of the current user
{% if flag?(:win32) %}
it "doesn't raise if environment variable is missing" do
with_env({HOME_ENV_KEY => nil}) do
Path.home
end
end
{% end %}
end
end
6 changes: 5 additions & 1 deletion spec/support/env.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
def with_env(**values)
def with_env(values : Hash)
old_values = {} of String => String?
begin
values.each do |key, value|
Expand All @@ -14,3 +14,7 @@ def with_env(**values)
end
end
end

def with_env(**values)
with_env(values.to_h) { yield }
end
12 changes: 12 additions & 0 deletions src/crystal/system/path.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Crystal::System::Path
# Returns the path of the home directory of the current user.
# def self.home : String
end

{% if flag?(:unix) %}
require "./unix/path"
{% elsif flag?(:win32) %}
require "./win32/path"
{% else %}
{% raise "No Crystal::System::Path implementation available" %}
{% end %}
5 changes: 5 additions & 0 deletions src/crystal/system/unix/path.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Crystal::System::Path
def self.home : String
ENV["HOME"]
end
end
17 changes: 17 additions & 0 deletions src/crystal/system/win32/path.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require "c/combaseapi"
require "c/knownfolders"
require "c/shlobj_core"

module Crystal::System::Path
def self.home : String
if home_path = ENV["USERPROFILE"]?
home_path
elsif LibC.SHGetKnownFolderPath(pointerof(LibC::FOLDERID_Profile), 0, nil, out path_ptr) == 0
home_path, _ = String.from_utf16(path_ptr)
LibC.CoTaskMemFree(path_ptr)
home_path
else
raise RuntimeError.from_winerror("SHGetKnownFolderPath")
end
end
end
4 changes: 4 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/combaseapi.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@[Link("ole32")]
lib LibC
fun CoTaskMemFree(pv : Void*)
end
5 changes: 5 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/knownfolders.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require "c/guiddef"

lib LibC
FOLDERID_Profile = GUID.new(0x5e6c858f, 0x0e22, 0x4760, UInt8.static_array(0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73))
end
6 changes: 6 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/shlobj_core.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "c/guiddef"

@[Link("shell32")]
lib LibC
fun SHGetKnownFolderPath(rfid : GUID*, dwFlags : DWORD, hToken : HANDLE, ppszPath : LPWSTR*) : DWORD
end
4 changes: 3 additions & 1 deletion src/path.cr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "crystal/system/path"

# A `Path` represents a filesystem path and allows path-handling operations
# such as querying its components as well as semantic manipulations.
#
Expand Down Expand Up @@ -1359,6 +1361,6 @@ struct Path

# Returns the path of the home directory of the current user.
def self.home : Path
new ENV["HOME"]
new(Crystal::System::Path.home)
end
end