Generating template with some logic via HStringTemplate - templates

Here is some invalid HStringTemplate syntax:
option_a = $options.a$
option_b = $options.b$
$if options.option_c_is_needed$
option_c = $option.c$
$end$
In other words, part of template file should be created only if specific predicate is true. How can it be achieved via HStringTemplate? If there is no way to do that in it, what libraries could be helpful here?
May be there is some analog of erubis mechanism with ability to use haskell code inside template files?

Hammar's comment is correct. See below:
*Main Text.StringTemplate> render $ setAttribute "optSet" False $ (newSTMP "OptSet: $if(optSet)$Option Is Set$else$Option Isn't Set$endif$" :: StringTemplate String)
"OptSet: Option Isn't Set"
*Main Text.StringTemplate> render $ setAttribute "optSet" True $ (newSTMP "OptSet: $if(optSet)$Option Is Set$else$Option Isn't Set$endif$" :: StringTemplate String)
"OptSet: Option Is Set"

Related

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

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.

How can i use asterisk(*) in command line arguments in groovy?

Helo!
I've a groovy script with one argument. The argument has to be a regex pattern. I want to use an * in the argument, but always got: The syntax of the command is incorrect.
This is an example (opt.p is the pattern getting from the arg):
def map1 = [e:'pine___apple', f:'pineapple']
map1.each { entry ->
entry.value.find(~/$opt.p/) { match -> println entry.value}
}
I want to use the script from the command line in this way:
groovy test -p pine_*a
And I expect that the result will be:
pine___apple
pineapple
I tried these solutions and more, but nothing works:
pine_*a
"pine_*a"
'pine_*a'
pine_\*a
'pine_\*a'
"pine_\*a"
pine_'\*'a
pine_"\*"a
pine_'\\*'a
pine_"\\*"a
Somebody knows how to solve this problem?
Thanks a lot!
EDIT:
I use:
Groovy Version: 2.4.6 JVM: 1.7.0_45 Vendor: Oracle Corporation OS: Windows 7 And I also use CliBuilder:
def cli = new CliBuilder()
cli.with
{
p('pattern', args: 1, required: false)
}
def opt = cli.parse(args)
The following groovy script:
def map = [e:'pine___apple', f:'pineapple']
map.each { k, v ->
v.find(~/${args[0]}/) { println "key: $k, value: $v -> ${v.find(args[0])}" }
}
prints the following on invocation:
$ groovy test.groovy pine_*a
Checking key: e, value: pine___apple -> pine___a
Checking key: f, value: pineapple -> pinea
or to exclude one of the map entries:
$ groovy test.groovy "pine_{1,9}a"
key: e, value: pine___apple -> pine___a
Not sure what the issue with your script is. Maybe the handling of opts.p like one of the comments suggests.
edit 1:
an alternative CliBuilder syntax to the one mentioned by Tim Yates in his answer:
def opt = new CliBuilder().with {
p longOpt: 'pattern', args: 1
parse(args)
}
edit 2: solution
as this is marked as the accepted answer, I will add what ended up being the solution here (I have a comment on this below, but comments are not immediately obvious when browsing for the solution).
This issue was caused by a bug in the windows groovy distribution startgroovy.bat file (see separate stackoverflow thread) which is used to launch groovy. The problem is with windows execution and parameter handling and it occurs before the groovy program even starts executing. There is a fair chance this has been fixed in later versions of groovy so it might be worth a try to upgrade to the latest version. If not, the answer in the above thread should make it possible to fix startgroovy.bat.
Think it's the way you're capturing your opt
I assume you're using the CliBuilder
And I suspect you've missed the args: 1 from the definition of the p parameter
def opt = new CliBuilder().with {
it.p('pattern', args: 1)
parse(args)
}
def map1 = [e:'pine___apple', f:'pineapple']
map1.each { entry ->
entry.value.find(~/$opt.p/) { match -> println entry.value}
}
Works as you describe it should for me...

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.

Error: Camlp4: Uncaught exception: Not_found

I am working on an Ocsigen example (http://ocsigen.org/tuto/manual/macaque).
I get an error when trying to compile the program, as follows.
File "testDB.ml", line 15, characters 14-81 (end at line 18, character 4):
While finding quotation "table" in a position of "expr":
Available quotation expanders are:
svglist (in a position of expr)
svg (in a position of expr)
html5list (in a position of expr)
html5 (in a position of expr)
xhtmllist (in a position of expr)
xhtml (in a position of expr)
Camlp4: Uncaught exception: Not_found
My code is:
module Lwt_thread = struct
include Lwt
include Lwt_chan
end
module Lwt_PGOCaml = PGOCaml_generic.Make(Lwt_thread)
module Lwt_Query = Query.Make_with_Db(Lwt_thread)(Lwt_PGOCaml)
let get_db : unit -> unit Lwt_PGOCaml.t Lwt.t =
let db_handler = ref None in
fun () ->
match !db_handler with
| Some h -> Lwt.return h
| None -> Lwt_PGOCaml.connect ~database:"testbase" ()
let table = <:table< users (
login text NOT NULL,
password text NOT NULL
) >>
..........
I used eliom-destillery to generate the basic files.
I used "make" to compile the program.
I've tried many different things and done a google search but I can't figure out the problem. Any hints are greatly appreciated.
Generally speaking, the error message indicates that CamlP4 does not know the quotation you used, here table, which is used in your code as <:table< ... >>. The quotations can be added by CamlP4 extensions pa_xxx.cmo (or pa_xxx.cma) modules. Unless you made a typo of the quotation name, you failed to load an extension which provides it to CamlP4.
According to http://ocsigen.org/tuto/manual/macaque , Macaque (or its underlying libraries? I am not sure since I have never used it) provides the quotation table. So you have to instruct CamlP4 to load the corresponding extension. I believe the vanilla eliom-destillery is minimum for the basic eliom programming and does not cover for the extensions for Macaque.
Actually the document http://ocsigen.org/tuto/manual/macaque points out it:
We need to reference macaque in the Makefile :
SERVER_PACKAGE := macaque.syntax
This should be the CamlP4 syntax extension name required for table.

Configuring C# out parameters with Foq in F#

I am using F# and Foq to write unit tests for a C# project.
I am trying to set up a mock of an interface whose method has an out parameter, and I have no idea how to even start. It probably has to do with code quotations, but that's where my understanding ends.
The interface is this:
public interface IGetTypeNameString
{
bool For(Type type, out string typeName);
}
In C# Foq usage for the interface looks like this:
[Fact]
public void Foq_Out()
{
// Arrange
var name = "result";
var instance = new Mock<IGetTypeNameString>()
.Setup(x => x.For(It.IsAny<Type>(), out name))
.Returns(true)
.Create();
// Act
string resultName;
var result = instance.For(typeof(string), out resultName);
// Assert
Assert.True(result);
Assert.Equal("result", resultName);
}
As for how to achieve that with F#, I am completely lost. I tried something along the lines of
let name = "result"
let instance = Mock<IGetTypeNameString>().Setup(<# x.For(It.IsAny<Type>(), name) #>).Returns(true).Create();
which results in the quotation expression being underlined with an error message of
This expression was expected to have type IGetTypeNameString -> Quotations.Expr<'a> but here has type Quotations.Expr<'b>
Without any indication what types a and b are supposed to be, I have no clue how to correct this.
:?>
(It gets even wilder when I use open Foq.Linq; then the Error List window starts telling me about possible overloads with stuff like Action<'TAbstract> -> ActionBuilder<'TAbstract>, and I get even loster....)
Any assistance or explanation greatly appreciated!
Edit:
So, as stated here, byref/out parameters can not be used in code quotations. Can this be set up at all then in F#?
Foq supports setting up of C# out parameters from C# using the Foq.Linq namespace.
The IGetTypeNameString interface can be easily setup in F# via an object expression:
let mock =
{ new IGetTypeNameString with
member __.For(t,name) =
name <- "Name"
true
}
For declarations that have no analog in F#, like C#'s protected members and out parameters, you can also use the SetupByName overload, i.e.:
let mock =
Mock<IGetTypeNameString>()
.SetupByName("For").Returns(true)
.Create()
let success, _ = mock.For(typeof<int>)