Hi, I have a question. For some needs in my project, it’s mandatory for me to require a file, but outside my project folder. How can I do that ?
I would like to use absolute path
Hi, I have a question. For some needs in my project, it’s mandatory for me to require a file, but outside my project folder. How can I do that ?
I would like to use absolute path
You can technically do that. Just use the absolute path instead of a relative one as argument to require
.
It’s probably a bad idea, though.
What do you think you need this for?
No I tried, but unfortunately it doesn’t work. The require instruction is always relative to the current path.
But no worries, I think I found maybe a better way to do what I need
Why crystal developers choose to restrict the require path to a relative path ? For security purpose ? I’m not sure to understand
Just to explain, when my software generate a task, it generate a crystal file, will interpreted by the crystal interpreter
I don’t think security is a factor (macro code and the program itself can still read any file on disk that the user has access to), but scoping required files to your project’s directory is important for portability. If my project expected a file outside the project’s directory, it would mean that my code can likely only be compiled on my machine — there is no guarantee that anyone else would have that Crystal source file in the same place on their hard drive when they pull down the project code. So it’s likely more that the functionality you’re asking about wouldn’t be useful (and would actually be confusing) for the nearly every use case.
If that’s what you really want, you can still achieve that through macros. You can put this somewhere in your app’s bootstrapping code (because macros have to be defined before they’re used):
macro require_absolute(path)
{{run "require_absolute", path}}
end
Then in require_absolute.cr
:
puts File.read(ARGV[0])
The run
macro compiles the given Crystal file and pipes its stdout in place into your Crystal code. This is how ECR
templates work.
One thing to keep in mind, though, is that an error in that file may not have as clean a stack trace as something loaded via require
. I think there’s a way to improve those stack traces but I’m not 100% sure.
Probably better to just use Crystal::Macros - Crystal 1.7.2 since you just want the contents of the file and aren’t doing anything more complex.
{{ read_file "/path/to/file.cr" }}
Using the run
macro isn’t great for compile times.
Ahh, I didn’t realize read_file
was there! Nice!
This is true on first compile, but it’ll use a cached version on subsequent compilations. Might not be true in CI, but a few extra seconds of CI time usually isn’t very noticeable.
So I did a test following of your advices.
I have a file located here: /home/zohran/Documents/Programmation/ISM/ISM/Default/Path.cr
It content:
module ISM
module Default
module Path
IsmDirectory = "ism/"
BinaryDirectory = "bin/"
ToolsDirectory = "tools/"
SourcesDirectory = "sources/"
RuntimeDataDirectory = "var/run/#{IsmDirectory}"
TemporaryDirectory = "tmp/#{IsmDirectory}"
SettingsDirectory = "etc/#{IsmDirectory}"
LogsDirectory = "var/log/#{IsmDirectory}"
LibraryDirectory = "usr/share/#{IsmDirectory}"
PortsDirectory = "#{RuntimeDataDirectory}ports/"
SoftwaresDirectory = "#{RuntimeDataDirectory}softwares/"
InstalledSoftwaresDirectory = "#{RuntimeDataDirectory}installedsoftwares/"
SettingsSoftwaresDirectory = "#{SettingsDirectory}softwares/"
BuiltSoftwaresDirectory = "#{TemporaryDirectory}builtsoftwares/"
end
end
end
I made an another file named test.cr at: /home/zohran/test.cr
It content:
{{ read_file "/home/zohran/Documents/Programmation/ISM/ISM/Default/Path.cr" }}
puts ISM::Default::Path::BinaryDirectory
But when I run it, I have this error:
zohran@alienware-m17-r3 ~ $ crystal test.cr
Showing last frame. Use --error-trace for full trace.
In test.cr:3:6
3 | puts ISM::Default::Path::BinaryDirectory
^----------------------------------
Error: undefined constant ISM::Default::Path::BinaryDirectory
Try like {{ read_file("/home/zohran/Documents/Programmation/ISM/ISM/Default/Path.cr").id }}
read_file
returns a StringLiteral
so have to use #id
to get a MacroId
so its treated as actual code.
Oh yeah perfect ! Thanks a lot
You really saved my life
I never thought i could require a file in a absolute path.
But, i consider we should improve the error output when user try to do it.
╰─ $ cr run 1.cr
Showing last frame. Use --error-trace for full trace.
In 1.cr:1:1
1 | require "/home/zw963/Crystal/play/2.cr"
^
Error: can't find file '/home/zw963/Crystal/play/2.cr'
If you're trying to require a shard:
- Did you remember to run `shards install`?
- Did you make sure you're running the compiler in the same directory as your shard.yml?
╭─ 18:00 zw963 ⮀ ~/Crystal/play ⮀ ➦ ruby-3.2.1
╰─ $ 1 file /home/zw963/Crystal/play/2.cr
/home/zw963/Crystal/play/2.cr: ASCII text