Project-relative `require`?

Thanks for awesome explain, i guess i can understand above code now.

  1. ROOT.id have value on compile time. as you said.
  2. macro will be expanded two time. when run src "config/env" the first time,
    it will expand {{ROOT.id}} first to

Basically, just expand {{file.id}} into config/env, expand {{ROOT.id}} to /home/common/Study/Crystal/test_macro

   macro load_src
      {% path = __DIR__.gsub(%r{\A/home/common/Study/Crystal/test_macro, "").gsub(%r{[^/]+}, "..").id %}
      require "{{path[1..]}}/src/config/env"
    end
    load_src

Then when run load_src the second time, above literal load_src macro will be run, then get expected result.

am i misunderstanding something? as a rubyist, I’m not the best, but I’m pretty good, i almost read done the documents in reference, the only challenge is macro.

No, you guessed exactly what happen here :slight_smile: .
As far as know, the compiler has the following behavior: while there are macro calls or {{ / {%, it expend these, until there are no more to expand. Then it executes semantics recursively on inner of defs, and expands macros inside each until there are no more.

Yeah macro are not the simplest part, but once mastered, it’s really a powerful feature, you can just does almost everything you want at compile time. (except perhaps coffee :sweat_smile:)

1 Like

Thank you for answer.

Still one more question, as in above example, we have to use nested macro to make it work
i guess the reason is, there is no way to make {{ROOT.id}} in regex to expand correct in this case.
because, {{ROOT.id}} should interpolate into the regex literal.

For only this reason, which we need nested macro, maybe overkill i think.

if there is another more simple way to do same things, but not need nested macro?

Yes that why. (It’s funny I made a post about a similar need recently)

All is not always directly possible with macro, but with some tricks it often possible to does what we need.

Actually, I found a solution without nested macro:

macro src(file)
  {% path = (__DIR__.starts_with?(ROOT) ? __DIR__[ROOT.size..] : __DIR__) %}
  {% path = path.gsub(%r{[^/]+}, "..").id  %}
  require "{{path[1..]}}/src/{{file.id}}"
end

src "./file.cr"
1 Like

In fact, we need use __DIR__[ROOT.size-1..] instead of __DIR__[ROOT.size..], and we don’t need the starts_with? check, because it always true.

following is my refactored version use your’s hack, it works.

{% begin %}
  ROOT = "{{system("pwd").strip.id}}"
{% end %}

{% begin %}
  # strip the ROOT part of spec_helper.cr, and replace the left folder name with ..
  # then strip the first dot.
  # e.g. it replace `/home/zw963/Study/Crystal/test_macro/src/config` to `./..`
  PATH = {{__DIR__[ROOT.size+1..].gsub(%r{[^/]+}, "..")[1..]}}
{% end %}

macro spec(file)
  require "{{PATH.id}}/spec/{{file.id}}"
end

macro src(file)
  require "{{PATH.id}}/src/{{file.id}}"
end

Though, i have one more question about macro. :smile:

  1. Why macro not designed to like ERB, that is, all {% … %} use same context scope, if that is true, we can replace the ROOT and PATH with it lowercase version, and use it in only one begin/end block, like this:
{% begin %}
  root = "{{system("pwd").strip.id}}"
  path = {{__DIR__[root.size+1..].gsub(%r{[^/]+}, "..")[1..]}}
{% end %}


macro spec(file)
  require "{{path.id}}/spec/{{file.id}}"
end

macro src(file)
  require "{{path.id}}/src/{{file.id}}"
end

Above code not work, with two error:

  1. code when define path variable could not see the root variable in same begin/end scope.
  2. code in macro could not see path variable in another begin/end scope.
In src/config/require.cr:20:14

 20 | require "{{path.id}}/src/{{file.id}}"
                 ^---
Error: undefined macro variable 'path'

Good your version is even more simpler!

It’s because each macro act independently and gets they own variables. I thinks it would be a mess if all macro vars were global. Under each call, it can have several macro triggered. The name collision would be big.

Something like that works too^^: (be careful to declare root inside a macro interpolation and not like a normal local var, this value would not be accessible at compile time :wink:).

{% begin %}
  {% root = system("pwd").strip.id %}
  PATH = {{__DIR__[root.size..].gsub(%r{[^/]+}, "..")[1..]}}
{% end %}

macro spec(file)
  require "{{PATH.id}}/spec/{{file.id}}"
end

macro src(file)
  require "{{PATH.id}}/src/{{file.id}}"
end
1 Like

Oops, i should have thought of it!

this should be the final(simplest version)

{% begin %}
  {% root = system("pwd").strip.id %}
  # strip the ROOT part of spec_helper.cr, and replace the left folder name with ..
  # then strip the first dot.
  # e.g. it replace `/home/zw963/Study/Crystal/test_macro/src/config` to `./..`
  PATH = {{__DIR__[root.size+1..].gsub(%r{[^/]+}, "..")[1..]}}
{% end %}

macro spec(file)
  require "{{PATH.id}}/spec/{{file.id}}"
end

macro src(file)
  require "{{PATH.id}}/src/{{file.id}}"
end

write this need so many knowledge of macro, i learned a lot, thank you very much!

3 Likes