How can I generate code from file at compile time using a macro? - crystal-lang

I have a CSV file that looks like this:
CountryCode,CountryName
AD,Andorra
AE,United Arab Emirates
AF,Afghanistan
AG,Antigua and Barbuda
// -- snip -- //
and a class that looks like this:
module OpenData
class Country
def initialize(#code : String, #name : String)
end
end
end
and I want to have a class variable within the module automatically loaded at compile time like this:
module OpenData
##countries : Array(Country) = {{ run "./sources/country_codes.cr" }}
end
I tried to use the "run" macro above with the following code:
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
but this results in
##countries : Array(Country) = {{ run "./sources/country_codes.cr" }}
^
Error: class variable '##countries' of OpenData must be Array(OpenData::Country), not Nil
All my other attempts somehow failed due to various reasons, like not being able to call .new within a macro or stuff like that. This is something I regularly do in Elixir and other languages that support macros and is something I would suspect Crystal can also achieve... I'd also take any other way that accomplishes the task at compile time!
Basically there are several more files I want to process this way, and they`re longer/more complex... thanks in advance!
EDIT:
Found the issue. It seems that I have to return a string that includes actual crystal code from the "run" macro. So, the code in the "run" file becomes:
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(", ")}]"
and everything works!

You already found your answer, but for completeness, here are the docs, from: https://crystal-lang.org/api/1.2.2/Crystal/Macros.html#run%28filename%2C%2Aargs%29%3AMacroId-instance-method
Compiles and execute a Crystal program and returns its output
as a MacroId.
The file denoted by filename must be a valid Crystal program.
This macro invocation passes args to the program as regular
program arguments. The program must output a valid Crystal expression.
This output is the result of this macro invocation, as a MacroId.
The run macro is useful when the subset of available macro methods
are not enough for your purposes and you need something more powerful.
With run you can read files at compile time, connect to the internet
or to a database.
A simple example:
# 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")
The above generates a program that will have the contents of some_file.txt.
The file, however, is read at compile time and will not be needed at runtime.

Related

EJS: Testing of included ejs files

my express app uses one path ("/") to display a huge page which was build using several includes. To avoid confusion, I would like to test each and every ejs file on its own. But as these ejs-includes are not reachable from the express app, I have no clue how to perform that task.
- index.ejs
- mainMenu.ejs
- systemsTable.ejs
- systemRow.ejs
- systemStatusIcon.ejs
- systemName.ejs
- ....
The more complex the index.ejs file gets the more I would like to test its parts. But how can I test the result of systemStatusIcon.ejs?
Thanks
Without any knowledge of what your code looks like, the I suggest setting up a configuration JSON file of test data objects each containing:
the EJS file path to be tested
render input data
an array of regular expressions to test against the rendered output
Then your test code can iterate over these objects and render and test each in turn.
Example Template Rendering
A simple piece of code to render a template looks like this:
const ejs = require('ejs');
let template = `
value is: <%- value %>
`;
const renderData = {
value: 123
};
const output = ejs.render(template, renderData);
console.log(output);
With output:
> node index.js
value is: 123
What renderData will be required is dependent on your existing templates. Template errors will be thrown as standard JS errors.
You could also use the renderFile function to do the loading for you.
ejs.renderFile(filename, data, options, function(err, str){
// str => Rendered HTML string
});
EJS docs can be found at: https://ejs.co/

How to get the name of the arguments passed into a block?

In Ruby, you can do this:
prc = lambda{|x, y=42, *other|}
prc.parameters #=> [[:req, :x], [:opt, :y], [:rest, :other]]
In particular, I'm interested in being able to get the names of the parameters which are x and y in the above example.
In Crystal, I have the following situation:
def my_method(&block)
# I would like the name of the arguments of the block here
end
How would one do this in Crystal?
While this already sounds weird in Ruby, there's no way to do it in Crystal since in your example the block already takes no arguments. The other problem is that such information is already lost after compilation. So we would need to access this at compile time. But you cannot access a runtime method argument at compile time. However you can access the block with a macro and that then even allows arbitrary signatures of the block without explicitly giving them:
macro foo(&block)
{{ block.args.first.stringify }}
end
p foo {|x| 0 } # => "x"
To expand on the great answer by Jonne Haß, the equivalent of the Ruby parameters method would be something like this:
macro block_args(&block)
{{ block.args.map &.symbolize }}
end
p block_args {|x, y, *other| } # => [:x, :y, :other]
Note that block arguments are always required in Crystal and can't have default values.

How to pass multiple arguments to custom written Robot Framework Keyword?

Custom Keyword written in python 2.7:
#keyword("Update ${filename} with ${properties}")
def set_multiple_test_properties(self, filename, properties):
for each in values.split(","):
each = each.replace(" ", "")
key, value = each.split("=")
self.set_test_properties(filename, key, value)
When we send paremeters in a single line as shown below, its working as expected:
"Update sample.txt with "test.update=11,timeout=20,delay.seconds=10,maxUntouchedTime=10"
But when we modify the above line with a new lines (for better readability) it's not working.
Update sample.txt with "test.update = 11,
timeout=20,
delay.seconds=10,
maxUntouchedTime=10"
Any clue on this please?
I am not very sure whether it will work or not, but please try like this
Update sample.txt with "test.update = 11,
... timeout=20,
... delay.seconds=10,
... maxUntouchedTime=10"
Your approach is not working, cause the 2nd line is considered a call to a keyword (called "timeout=20,"), the 3rd another one, and so on. The 3 dots don't work cause they are "cell separators" - delimiter b/n arguments.
If you are going for readability, you can use the Catenate kw (it's in the Strings library):
${props}= Catenate SEPARATOR=${SPACE}
... test.update = 11,
... timeout=20,
... delay.seconds=10,
... maxUntouchedTime=10
, and then call your keyword with that variable:
Update sample.txt with "${props}"
btw, a) I think your keyword declaration in the decorator is without the double quotes - i.e. called like that ^ they'll be treated as part of the argument's value, b) there seems to be an error in the py method - the argument's name is "properties" while the itterator uses "values", and c) you might want to consider using named varargs (**kwargs in python, ${kwargs} in RF syntax) for this purpose (sorry, offtopic, but couldn't resist :)

How to import Shakespearean Templates in Yesod?

I was using QuasiQuotations in Yesod, and everything worked fine. BUT my file became very large and not nice to look at. Also, my TextEditor does not highlight this syntax correctly. That is why is split my files like so:
getHomeR :: Handler Html
getHomeR = do
webSockets chatApp
defaultLayout $ do
$(luciusFile "templates/chat.lucius")
$(juliusFile "templates/chat.julius")
$(hamletFile "templates/chat.hamlet")
If this is wrong, please do tell. Doing runghc myFile.hs throws many errors like this:
chat_new.hs:115:9:
Couldn't match expected type ‘t0 -> Css’
with actual type ‘WidgetT App IO a0’
The lambda expression ‘\ _render_ajFK
-> (shakespeare-2.0.7:Text.Css.CssNoWhitespace . (foldr ($) ...))
...’
has one argument,
but its type ‘WidgetT App IO a0’ has none
In a stmt of a 'do' block:
\ _render_ajFK
...
And this.
chat_new.hs:116:9:
Couldn't match type ‘(url0 -> [(Text, Text)] -> Text)
-> Javascript’
with ‘WidgetT App IO a1’
Expected type: WidgetT App IO a1
Actual type: JavascriptUrl url0
Probable cause: ‘asJavascriptUrl’ is applied to too few arguments
...
And also one for the HTML (Hamlet).
Thus, one per template.
It seems that hamletFile and others treat templates as self-contained, while yours are referencing something from each other. You can play with order of *File calls, or use widgetFile* from Yesod.Default.Util module:
$(widgetFileNoReload def "chat")
The Reload variant is useful for development - it would make yesod devel to watch for file changes and reload them.

I am new to RUBY and i need to understand 3 functions

I have been given the 3 functions below. Can anybody please help me to understand these? I am trying to port an application to C++ using Qt, but I don't understand these functions. So please help me!
Thanks in advance.
function 1:
def read_key
puts "read pemkey: \"#{#pkey}\"" if #verbose
File.open(#pkey, 'rb') do |io|
#key = OpenSSL::PKey::RSA.new(io)
end
end
function 2:
def generate_key
puts "generate pemkey to \"#{#pkey_o}\"" if #verbose
#key = OpenSSL::PKey::RSA.generate(KEY_SIZE)
# save key
File.open(#pkey_o, 'wb') do |file|
file << #key.export()
end
end
function 3:
def sign_zip
puts "sign zip" if #verbose
plain = nil
File.open(#zip, 'rb') do |file|
plain = file.read
end
#sig = #key.sign(OpenSSL::Digest::SHA1.new, plain)
end
There are probably two things about the above code that are confusing you, which if clarified, will help understand it.
First, #verbose and #key are instance variables, what a C++ programmer might call "member variables." The "if #verbose" following the puts statement literally means only do the puts if #verbose is true. #verbose never needs to be declared a bool--you just start using it. If it's never initialized, it's "nil" which evaluates to false.
Second, the do/end parts are code blocks. Many Ruby methods take a code block and execute it with a variable declared in those pipe characters. An example would be "array.each do |s| puts s; end" which might look like "for(int i = 0; i < array.size(); ++i) { s = array[i]; puts(s); }" in C++. For File.open, |io| is the file instance opened, and "read" is one of its methods.
These are all methods. #{#pkey_o} is string interpolation, substituting in the contents of an instance variable (called pkey_o; Ruby instance variables begin with # and class variables – unused here – begin with ##).
File.open(#pkey, 'rb') do |io|
#key = OpenSSL::PKey::RSA.new(io)
end
That opens the file whose name is stored in #pkey, stores the file handle in io (a block-local variable) and uses that with OpenSSL::PKey::RSA.new, whose result is stored in #key. Finally, it closes the file handle when the block is finished (at the end) whether or not it is a successful exit or an error case (in which case an exception would be thrown, but it would still be thrown). When translating this to C++, use of the RAII pattern is entirely reasonable (if you were going to Java, I'd say to use try/finally).