I'm creating a document generator from YAML input in Go. It needs to specify which line of the YAML file each item/node is generated from. Is there a way to achieve it in Go?
For example here's a YAML file
- key1: item 1
key2: item 2
- key1: another item 1
key2: another item 2
and I'd like to see the following
[
{'__line__': 1, 'key1': 'item 1', 'key2': 'item 2'},
{'__line__': 3, 'key1': 'another item 1', 'key2': 'another item 2'},
]
I see a similar question answered for Python Parsing YAML, return with line number but I'm missing how to make use of https://pkg.go.dev/gopkg.in/yaml.v3
In Go this is possible by implementing a custom Unmarshaler and setting the line number manually.
So let's build the data structure we need for the file:
type ListItem struct {
Line int
ListItemData
}
type ListItemData struct {
Key1 string `yaml:"key1"`
Key2 string `yaml:"key2"`
}
Now we implement the Unmarshaler interface by creating a method on the ListItem type:
func (li *ListItem) UnmarshalYAML(value *yaml.Node) error {
err := value.Decode(&li.ListItemData)
if err != nil {
return err
}
// Save the line number
li.Line = value.Line
return nil
}
You will notice that I created an inner struct and only decode the data to that struct. This is because if we were to call Decode on li itself, we would get a stack overflow, as decoding calls the UnmarshalYAML method repeatedly.
You can try this online and see that the line numbers are set correctly.
Related
Is there any way to have a foreach outside a function body for ex. generating code.
My scenario is that I have an associative array at compile-time that I need to use to generate specific fields. Unfortunately I can't really use a foreach outside of a function body to generate the members.
Right now I am using a work-around where I have a few mixins where I give one AA to the first mixin and that mixin converts the AA to an array and passes it to the second mixin, the second mixin is recursive with itself by ng itself until there is no more member, while also calling the first member in the array, which calls a third mixin that I can use to generate code.
It's not as smooth and dynamic as I really would want it to be and I was wondering if anyone has a better solution.
Here is my solution
// First mixin that takes the associative array and a string for the mixin to handle the items
mixin template StaticForEachAA(TKey,TValue, TKey[TValue] enumerator, string itemMixin) {
enum array = arrayAA!(TKey,TValue)(enumerator); // Converts the AA to an array of key-value pair structs
alias ArrayType = KeyValuePair!(TKey,TValue);
mixin StaticForEachArray!(ArrayType, array, itemMixin); // Calls second mixin with the array
}
// The second mixin that "fake loops" the array
mixin template StaticForEachArray(T, T[] array, string itemMixin) {
static if (array.length) {
import std.string : format;
mixin(format("mixin %s!(T, array[0]);", itemMixin)); // Mixins the itemMixin to handle the current item
mixin StaticForEachArray!(T, array[1 .. $], itemMixin); // slices the array to remove the item we just handled
}
}
// The third mixin that can be used to do whatever has to be done with item
mixin template StaticForEachItem(T, T item) {
import std.conv : to;
pragma(msg, to!string(item));
}
And to do the "fake foreach" for an associative
enum AA = [0 : 1, 1 : 2, 2 : 3];
mixin StaticForEachAA!(int, int, AA, "StaticForEachItem");
This will print the key-value pairs from AA at compile-time.
Leveraging the power of compile-time functione excution (CTFE) you could make a helper function that generates code for you using the data from an associative array (AA) you provide.
import std.string : format;
string generateCodeForEachAA(TKey, TValue)(TValue[TKey] data, string foreachBody)
{
string result;
foreach(k, v ; data)
{
result ~= format(foreachBody, k, v);
}
return result;
}
This function turns the given AA data into a string by formatting the given foreachBody with each AA element. The returned string can then be mixin'ed:
enum Data = [ 0 : 1, 1 : 2, 2 : 3 ];
enum Code = generateCodeForEachAA(Data, q{ pragma(msg, "%1$s => %2$s"); });
pragma(msg, "Code: " ~ Code);
mixin(Code);
Output:
Code: pragma(msg, "0 => 1"); pragma(msg, "1 => 2"); pragma(msg, "2 => 3");
0 => 1
1 => 2
2 => 3
Using this to generate members within a struct:
struct Foo
{
enum Members = [ "foo": 123, "bar": 42 ];
mixin(generateCodeForEachAA(Members, q{ typeof(%2$s) %1$s = %2$s; }));
}
void main()
{
import std.stdio : writeln;
Foo f;
writeln("foo: ", f.foo);
writeln("bar: ", f.bar);
}
Output:
foo: 123
bar: 42
I am using Grails excel import plugin to import an excel file.
static Map propertyConfigurationMap = [
name:([expectedType: ExcelImportService.PROPERTY_TYPE_STRING, defaultValue:null]),
age:([expectedType: ExcelImportService.PROPERTY_TYPE_INT, defaultValue:0])]
static Map CONFIG_BOOK_COLUMN_MAP = [
sheet:'Sheet1',
startRow: 1,
columnMap: [
//Col, Map-Key
'A':'name',
'B':'age',
]
]
I am able to retrieve the array list by using the code snippet:
def usersList = excelImportService.columns(workbook, CONFIG_USER_COLUMN_MAP)
which results in
[[name: Mark, age: 25], [name: Jhon, age: 46], [name: Anil, age: 62], [name: Steve, age: 32]]
And also I'm able to read each record say [name: Mark, age: 25] by using usersList.get(0)
How do I read the each column value?
I know I can read something like this
String[] row = usersList.get(0)
for (String s : row)
println s
I wonder is there any thing that plugin supports so that I can read column value directly rather manipulating it to get the desired result.
Your usersList is basically a List<Map<String, Object>> (list of maps). You can read a column using the name you gave it in the config. In your example, you named column A name and column B age. So using your iteration example as a basis, you can read each column like this:
Map row = usersList.get(0)
for(Map.Entry entry : row) {
println entry.value
}
Groovy makes this easier to do with Object.each(Closure):
row.each { key, value ->
println value
}
If you want to read a specific column value, here are a few ways to do it:
println row.name // One
println row['name'] // Two
println row.getAt('name') // Three
Hint: These all end up calling row.getAt('name')
I took a look at the postgresql driver dokumentation. I found the following code there.
conn.query('select color from crayons where id = #id', {'id': 5})
.toList()
.then((result) { print(result); });
conn.execute('insert into crayons values (#id, #color)',
{'id': 1, 'color': 'pink'})
.then((_) { print('done.'); });
I wanted to test the inserting of the id into the string and put the following code into try.dart.org
// Go ahead and modify this example.
void main() {
var string = 'select color from crayons where id = #id', {'id': 5};
print(string);
}
Unfortunately this gives me the following error Compilation failed: Expected identifier, but got '{'.. I also tried a few abbreviations but nothing helped.
So the question is. How do I properly insert the values of a map into a string?
Your example code from the PostgreSQL driver means that the query() method expects two arguments, a string and a map.
Your 2nd example seems to try to do string interpolation. In Dart this would look like
var id = 5;
var string = 'select color from crayons where id = $id'; // or ${id}
print(string);
Please don't try this for SQL. This opens a big hole for SQL injection attachs.
PostgresSQL does it's own string interpolation in a safe way.
I have raw data that is contained in 3 separate lines. I want to build a single map record using parts of each line. I then read the next 3 lines and create the next map record and so on. All the groovy examples I've found on maps show them being created from data on a single line, or possibly i am misunderstanding the examples. Here is what the raw data looks like.
snmp v2: data result = "Local1"
snmp v2: data result ip = "10.10.10.121"
snmp v2: data result gal = "899"
new
snmp v2: data result = "Local2"
snmp v2: data result ip = "192.168.10.2"
snmp v2: data result gal = "7777"
new
I want to put this data into a map. In this example Local1 and Local2 would be keys and they would each have 2 associated values. I will show you my latest attempt but it is little more then a guess that failed.
def data = RAW
def map = [:]
data.splitEachLine("="){
it.each{ x ->
map.put(it[0], it[1])
map.each{ k, v -> println "${k}:${v}" } }}
The desired output is:
[ Local1 : [ ip: "10.10.10.121", gal: "899" ],
Local2: [ ip: "192.168.10.2", gal: "7777" ] ]
You can build a new data structure from an existing one using aggregate operations defined on collections; collect produces a list from an existing list, collectEntries creates a map from a list.
The question specifies there are always three lines for an entry, followed by a line with "new" on it. If I can assume they're always in the same order I can grab the last word off each line, use collate to group every four lines into a sublist, then convert each sublist to a map entry:
lines = new File('c:/temp/testdata.txt').readLines()
mymap = lines.collect { it.tokenize()[-1] }
.collate(4)
.collectEntries { e-> [(e[0].replace('"', ''))) : [ip: e[1], gal: e[2]]] }
which evaluates to
[Local1:[ip:"10.10.10.121", gal:"899"], Local2:[ip:"192.168.10.2", gal:"7777"]]
or remove all the quotes in the first step:
mymap = lines.collect { (it.tokenize()[-1]).replace('"', '') }
.collate(4)
.collectEntries { e-> [(e[0]) : [ip: e[1], gal: e[2]]] }
in order to get
[Local1:[ip:10.10.10.121, gal:899], Local2:[ip:192.168.10.2, gal:7777]]
If you want to get a nested map as suggested by dmahapatro try this:
def map = [:]
data=data.eachLine() { line ->
if(line.startsWith("new")) return
tokens=line.replace("snmp v2: data","").split("=")
tokens=tokens.collect() { it.trim().replace("result ","").replaceAll(/"/, "") }
if(tokens[0]=="result") {
nested=[:]
map[tokens[1]]=nested
}
else
nested[tokens[0]]=tokens[1]
}
println("map: $map")
here we:
iterate over lines
skip lines with "new" at the beginning
remove "snmp v2: data" from the text of the line
split each line in tokens, trim() each token, and remove "result " and quotes
tokens are in pairs and now look like:
result, Local1
ip, 10.10.10.121
gal, 899
next when the first token is "result", we build a nested map and place in the main map at the key given by the value of token[1]
otherwise we populate the nested map with key=token[0] and value=token[1]
the result is:
map: [Local1:[ip:10.10.10.121, gal:899], Local2:[ip:192.168.10.2, gal:7777]]
edit: fixed to remove quotes
(lord help me for programming in AS2)
i'm itterating thru an array of text field objects and tracing the selected focus field when pressing tab, as well as each object.
i'm trying to equate these object, but while they trace the exact same they are not.
m_InputFieldsArray = new Array(m_TitleTextInput, m_CommentsTextArea, m_EmailTextInput);
for (var i:Number = 0; i < m_InputFieldsArray.length; i++)
{
trace("Get Focus: " + Selection.getFocus());
trace("Arr Index: " + m_InputFieldsArray[i].textField);
if (Selection.getFocus() == m_InputFieldsArray[i].textField)
{
trace("Match!");
return;
}
else
{
trace("NO Match!");
}
}
the output:
Get Focus: _level0.m_Window.form.m_TitleTextInput.textField
Arr Index: _level0.m_Window.form.m_TitleTextInput.textField
NO Match!
Get Focus: _level0.m_Window.form.m_TitleTextInput.textField
Arr Index: _level0.m_Window.form.m_CommentsTextArea.textField
NO Match!
Get Focus: _level0.m_Window.form.m_TitleTextInput.textField
Arr Index: _level0.m_Window.form.m_EmailTextInput.textField
NO Match!
the first group traces the same, but apparently they do not match. Selection.getFocus() returns a string, while the array index is tracing the text field object. if i add toString() to the text field object it will trace as [Object object]
how can i accomplish a match?
Use eval() for the Selection.getFocus()
An alternative, if you don't want to use eval(), that has a bad reputation, to get the same kind of string representation as Selection.getFocus() returns, you can use "" + m_InputFieldsArray[i].textField. It won't return "[Object object]", as toString() does.
That is basically what you see in your trace calls, that a string concatenated with the object reference gives the path to the object, rather than .toString() on the object.
I can't test AS2 right now, but I'm pretty sure that is how it works. So you could do something like this:
if (Selection.getFocus() == "" + m_InputFieldsArray[i].textField)