我有一个看起来像这样的CSV文件:
CountryCode,CountryName
AD,Andorra
AE,United Arab Emirates
AF,Afghanistan
AG,Antigua and Barbuda
// -- snip -- //
以及如下所示的类:
module OpenData
class Country
def initialize(@code : String, @name : String)
end
end
end
我想在编译时自动加载模块中的类变量,如下所示:
module OpenData
@@countries : Array(Country) = {{ run "./sources/country_codes.cr" }}
end
我尝试使用以下代码使用上面的"运行"宏:
require "csv"
require "./country"
content = File.read "#{__DIR__}/country-codes.csv"
result = [] of OpenData::Country
CSV.new(content, headers: true).each do |row|
result.push OpenData::Country.new(row["CountryCode"], row["CountryName"])
end
result
但这导致
@@countries : Array(Country) = {{ run "./sources/country_codes.cr" }}
^
Error: class variable '@@countries' of OpenData must be Array(OpenData::Country), not Nil
由于各种原因,我所有其他尝试都以某种方式失败了,例如无法在宏中调用.new
或类似的东西。这是我经常在Elixir和其他支持宏的语言中做的事情,我怀疑Crystal也可以实现......我也会采取任何其他方式在编译时完成任务!
基本上还有几个我想以这种方式处理的文件,它们更长/更复杂......提前感谢!
编辑:
发现问题。似乎我必须返回一个字符串,其中包含来自"run"宏的实际晶体代码。因此,"run"文件中的代码变为:
require "csv"
content = File.read "#{__DIR__}/country-codes.csv"
lines = [] of String
CSV.new(content, headers: true).each do |row|
lines << "Country.new("#{row["CountryCode"]}", "#{row["CountryName"]}")"
end
puts "[#{lines.join(", ")}]"
一切正常!
您已经找到了答案,但为了完整起见,以下是来自以下文档的文档: https://crystal-lang.org/api/1.2.2/Crystal/Macros.html#run%28filename%2C%2Aargs%29%3AMacroId-instance-method
编译并执行 Crystal 程序并返回其输出 作为
MacroId
.文件名表示的文件必须是有效的Crystal 程序。 此宏调用将参数作为常规传递给程序 程序参数。程序必须输出有效的晶体表达式。 此输出是此宏调用的结果,作为
MacroId
。当可用宏方法的子集时,
run
宏很有用 不足以满足您的目的,您需要更强大的东西。 使用run
您可以在编译时读取文件,连接到互联网 或数据库。一个简单的例子:
# read.cr puts File.read(ARGV[0])
# main.cr macro read_file_at_compile_time(filename) {{ run("./read", filename).stringify }} end puts read_file_at_compile_time("some_file.txt")
上面生成一个程序,该程序将具有
some_file.txt
的内容。 但是,该文件是在编译时读取的,在运行时不需要。