How do I extract values from a width delimited table in Ansible? - parsing

I'm trying to extract values from the result of an SQL command that is printed to stdout.
first second
29494060 23004496
29774383 22979864
I want to parse it into a data structure that looks something like this
[
{ first: 29494060, second: 23004496 },
{ first: 29774383, second: 22979864 }
]
So that I can access the values for each record like {{ item.first }} and {{ item.second }}.
I have been able to extract a single record by creating an object literal with values extracted with the regex_replace filter.
- set_fact:
items: { first: "{{ item | regex_replace('\\s*?(\\d+)\\s*?(\\d+)', '\\1') }}", second: "{{ item | regex_replace('\\s*?(\\d+)\\s*?(\\d+)', '\\2') }}" }
loop: '{{ command_out.stdout_lines }}'
This only keeps the result last line. I have not been able to create a list for each result. I have not been able to use list concatenation like items: "{{ items | default([]) + [{…}] }}" because of nested Jinja templates.
How do I extract a printed table of data into a list of objects?

Create keys. For example
- set_fact:
keys: "{{ command_out.stdout_lines[0].split() }}"
- debug:
var: keys
gives
"keys": [
"first",
"second"
]
then add the dictionaries, created by the filters dict and zip, to the list sql_list
- set_fact:
sql_list: "{{ sql_list|default([]) +
[dict(keys|zip(item.split()))] }}"
loop: "{{ command_out.stdout_lines[1:] }}"
- debug:
var: sql_list
gives
"sql_list": [
{
"first": "29494060",
"second": "23004496"
},
{
"first": "29774383",
"second": "22979864"
}
]
The task simplified by custom plugins gives the same result
- set_fact:
sql_list: "{{ command_out.stdout_lines[1:]|
map('string_split')|
map('list_dict_zip_rev', keys)|
list }}"
$ cat filter_plugins/string_filters.py
def string_split(s, *i):
if len(i) == 0:
return s.split()
elif len(i) == 1:
return s.split(i[0])
else:
return s.split(i[0], i[1])
def list_dict_zip_rev(l,k):
return dict((y,x) for x,y in zip(l,k))
class FilterModule(object):
''' Ansible filters.'''
def filters(self):
return {
'list_dict_zip_rev' : list_dict_zip_rev,
'string_split' : string_split
}

Related

Elasticsearch saves document as string of array, not array of strings

I am trying to contain array as a document value.
I succeed it in "tags" field as below;
This document contains array of strings.
curl -XGET localhost:9200/MY_INDEX/_doc/132328908
#=> {
"_index":"MY_INDEX",
"_type":"_doc",
"_id":"132328908",
"found":true,
"_source": {
"tags": ["food"]
}
}
However, when I am putting items in the same way as above,
the document is SOMETIMES like that;
curl -XGET localhost:9200/MY_INDEX/_doc/328098989
#=> {
"_index":"MY_INDEX",
"_type":"_doc",
"_id":"328098989",
"found":true,
"_source": {
"tags": "[\"food\"]"
}
}
This is string of array, not array of strings, which I expected.
"tags": "[\"food\"]"
It seems that this situation happens randomly and I could not predict it.
How could it happen?
Note:
・I use elasticsearch-ruby client to index a document.
This is my actual code;
es_client = Elasticsearch::Client.new url: MY_ENDPOINT
es_client.index(
index: MY_INDEX,
id: random_id, # defined elsewhere
body: {
doc: {
"tags": ["food"]
},
}
)
Thank you in advance.

Filtering by comparing two streams one-on-one in jq

I have streams
{
"key": "a",
"value": 1
}
{
"key": "b",
"value": 1
}
{
"key": "c",
"value": 1
}
{
"key": "d",
"value": 1
}
{
"key": "e",
"value": 1
}
And
(true,true,false,false,true)
I want to compare the two one-on-one and only print the object if the corresponding boolean is true.
So I want to output
{
"key": "a",
"value": 1
}
{
"key": "b",
"value": 1
}
{
"key": "e",
"value": 1
}
I tried (https://jqplay.org/s/GGTHEfQ9s3)
filter:
. as $input | foreach (true,true,false,false,true) as $dict ($input; select($dict))
input:
{
"key": "a",
"value": 1
}
{
"key": "b",
"value": 1
}
{
"key": "c",
"value": 1
}
{
"key": "d",
"value": 1
}
{
"key": "e",
"value": 1
}
But I get output:
{"key":"a","value":1}
{"key":"a","value":1}
null
{"key":"b","value":1}
{"key":"b","value":1}
null
{"key":"c","value":1}
{"key":"c","value":1}
null
{"key":"d","value":1}
{"key":"d","value":1}
null
{"key":"e","value":1}
{"key":"e","value":1}
null
Help will be appreciated.
One way would be to read in the streams as arrays, use transpose to match their items, and select by one and output the other:
jq -s '[.,[(true,true,false,false,true)]] | transpose[] | select(.[1])[0]' objects.json
Demo
Another approach would be to read in the streams as arrays, convert the booleans array into those indices where conditions match, and use them to reference into the objects array:
jq -s '.[[(true,true,false,false,true)] | indices(true)[]]' objects.json
Demo
The same approach but using nth to reference into the inputs stream requires more precaution, as the successive consumption of stream inputs demands the provision of relative distances, not absolute positions to nth. A conversion can be implemented by successively checking the position of the next true value using index and a while loop:
jq -n 'nth([true,true,false,false,true] | while(. != []; .[index(true) + 1:]) | index(true) | values; inputs)' objects.json
Demo
One could also use reduce to directly iterate over the boolean values, and just select any appropriate input:
jq -n 'reduce (true,true,false,false,true) as $dict ([]; . + [input | select($dict)]) | .[]' objects.json
Demo
A solution using foreach, like you intended, also would need the -n option to not miss the first item:
jq -n 'foreach (true,true,false,false,true) as $dict (null; input | select($dict))' objects.json
Demo
Unfortunately, each invocation of jq can currently handle at most one external JSON stream. This is not usually an issue unless both streams are very large, so in this answer I'll focus on a solution that scales. In fact, the amount of computer memory required is miniscule no matter how large the streams may be.
For simplicity, let's assume that:
demon.json is a file consisting of a stream of JSON boolean values (i.e., not comma-separated);
object.json is your stream of JSON objects;
the streams have the same length;
we are working in a bash or bash-like environment.
Then we could go with:
paste -d '\t' demon.json <(jq -c . objects.json) | jq -n '
foreach inputs as $boolean (null; input; select($boolean))'
So apart from the startup costs of paste and jq, we basically only need enough memory to hold one of the objects in objects.json at a time. This solution is also very fast.
Of course, if objects.json were already in JSONL (JSON-lines) format, then the first call to jq above would not be necessary.

Use EXTRACT(Keys) or other method to get a Nodes properties as key:value pairing with Cypher in Neo4j

I want to use a cypher query to return results in a format that I can use with D3. What I have below is working fine but I want to be able to include the properties of the nodes as "key:value" pairs directly after the labels are printed out. I can't explicitly code this because different nodes can have different properties e.g. I can't just add (prop1: l1.prop1, prop2: l1.prop2 .....).
MATCH (l0) -[r]-> (l1)
WHERE ID(l0) = #
RETURN
[
{
type: "node",
id: id(l0),
labels: labels(l0),
},
{
type: "node",
id: id(l1),
labels: labels(l1)
}
] as nodes,
[
{
startNodeId: ID(startNode(r)),
endNodeId: ID(endNode(r)),
relType: type(r)
}
] as relationship
I came accross this example on the forum which is close to what I want:
MATCH (n) WHERE id(n)=#
RETURN EXTRACT(key IN keys(n) | {key: key, value: n[key]})
This results in the following:
[{"key":"name","value":"Test Node 1"}]
where as I want to have just
[{"name":"Test Node 1"}]
I am using the KOA-NEO4J library so connecting to Neo4j over Bolt if that makes any difference.
Thanks a lot,
Cypher itself does not allow for dynamically creating the key of a map, however you can use the APOC function apoc.map.fromPairs to accomplish this. So your example above becomes:
MATCH (n) WHERE id(n) = #
apoc.map.fromPairs([key IN keys(n) | [key, n[key]]])
And your larger query becomes:
MATCH (l0) -[r]-> (l1)
WHERE ID(l0) = 1
RETURN
[
{
type: "node",
id: id(l0),
labels: labels(l0),
props: apoc.map.fromPairs([key IN keys(l0) | [key, l0[key]]])
},
{
type: "node",
id: id(l1),
labels: labels(l1),
props: apoc.map.fromPairs([key IN keys(l1) | [key, l1[key]]])
}
] as nodes,
[
{
startNodeId: ID(startNode(r)),
endNodeId: ID(endNode(r)),
relType: type(r)
}
] as relationship

Hierarchical Cypher query with literal result

My object structure looks like this: there are Containers as a root nodes, which have subcontainers. Each subcontainer has tasks, which also have subtasks. To illustracte this in Cypher MATCH:
(container)-[:HAS_SUBCONTAINER]->(subcont)-[:HAS_TASK]->(task)-[:HAS_TASK]->(subtask)
I want to write a query which returns JSON literal representation for this hierachy:
{
name: 'Main',
subcontainers: [
{
name: 'Subcont',
tasks: [
{
name: 'parent1',
children: [
{
name: 'child1'
}
]
}
]
}
]
}
Is there a way to do this in Cypher with one query? I found solution for one level hierarchy:
MATCH (cnt:Container {name: 'Main'})-[:HAS_SUBCONTAINER]->(subcnt)
RETURN { name: cnt.name, subcontainers: EXTRACT(subc IN collect(subcnt)|{name: subc.name})}
But can't fugure out how to do it for more complex case. Any ideas?
Given you know the depth of your matched pattern, you could do the nesting in a series of collects:
MATCH (container)-[:HAS_SUBCONTAINER]->(subcont)-[:HAS_TASK]->(task)-[:HAS_TASK]->(subtask)
WITH container, subcont, task, collect({name:subtask.name}) AS subtasks
WITH container, subcont, collect({name:task.name, children:subtasks}) AS tasks
WITH container, collect({name:subcont.name, tasks:tasks}) AS subconts
RETURN {
name: container.name,
subcontainers: subconts
} AS result
If the subtasks are option (the question by #jim-biard), then you'd need an optional match and a case statement:
MATCH (container)-[:HAS_SUBCONTAINER]->(subcont)-[:HAS_TASK]->(task)
OPTIONAL MATCH (task)-[:HAS_TASK]->(subtask)
WITH container, subcont, task, collect(
CASE subtask
WHEN NULL THEN NULL
ELSE { name:subtask.name }
END
) AS subtasks
WITH container, subcont, collect({name:task.name, children:subtasks}) AS tasks
WITH container, collect({name:subcont.name, tasks:tasks}) AS subconts
RETURN {
name: container.name,
subcontainers: subconts
} AS result

Submitted form text fields not grouping correctly

In my GSP I have a form with a lot textfields populated by a map that came from the controller, let me put this into an example, because my actual form is a lot more complicated than this:
for example, if I use users to populate a bunch of textfields where I can enter each person's age, I grouped them up into a map called data, and I want to process and save all that information after submitting:
<g:form useToken="true" name='example_form' action='submit'>
<g:each in='${users}' var='user' status='i'>
<g:textField name="data.${user.id}.name" value="${i.name}">
<g:field name="data.${user.id}.age" value="">
</g:each>
<button>Submit</button>
</g:form>
But when I am printing out the params.data in my submit controller, I noticed that not only I am getting the data map that I've created, I am also getting a bunch of garbage within it:
for(i in params.data){
println "key: ${i.key} value: ${i.value}"
}
output:
key: 0.name value: john
key: 0 value: [age: 35, name: john]
key: 1.name value: liz
key: 1 value: [age: 24, name: liz]
key: 2.name value: robert
key: 3.name value: david
key: 0.age value: 35
key: 1.age value: 24
key: 2 value: [age: 44, name: robert]
key: 3 value: [age: 23, name: david]
key: 3.age value: 23
key: 2.age value: 44
Am I doing something wrong?
expected output:
key: 0 value: [age: 35, name: john]
key: 1 value: [age: 24, name: liz]
key: 2 value: [age: 44, name: robert]
key: 3 value: [age: 23, name: david]
It should work exactly this way. When you're submitting data from your form, the body of your POST request looks this way:
data.0.name=john&data.0.age=35&data.1.name=liz&data.1.age=24&data.2.name=robert&data.2.age=44&data.3.name=david&data.3.age=23
So, it's just a plain string, representing a plain key-value map and Grails could parse is just like that:
['data.0.name': 'john', 'data.0.age': '35', 'data.1.name': 'liz', 'data.1.age': '24', 'data.2.name': 'robert', 'data.2.age': '44', 'data.3.name': 'david', 'data.3.age': '23']
But Grails developers wanted to simplify programmers' life, and they decided that if the key contains a dot, the request may represent some kind of structured data. So they decided to put it to the map, in addition to the raw request data. Thus, the dot can be interpreted in two ways - as a plain symbol, or as a separator between map name and map key. And it's up to developer which way the dot should be interpreted.
If you prefer to have clearer params over the easy use like def name = params.data.0.name, then you can use "_" insted of ".". In the controller you can use split("_") in a loop.
In a previous post #Alexander Tokarev explained what happened. The solution is an if statement as shown below:
for(i in params.data){
if( i.key.isNumber() ) {
println "key: ${i.key} value: ${i.value}"
}
}

Resources