Say I have a YML file original.yml with an array of objects
array_in_yml:
- start: 1
- middle: 2
- end: 3
I included it in modified.yml
!include "original.yml"
array_in_yml: []
I am expecting this array to be empty when I load the modified.yml, but it seems to have 3 values as original.yml. How do I force/override the array to be empty?
The discussion about !include seems to lead a bit away from the actual question. Let's assume that in some unknown way, the !include line gets replaced with the content in original.yml. We would have:
array_in_yml:
- start: 1
- middle: 2
- end: 3
array_in_yml: []
This is not valid YAML, since every key in a dictionary must be unique, but you use the key array_in_yml twice. Your YAML processor might ignore this and simply assign the first value (which is a sequence of three items) to the key array_in_yml.
Now the important part: There is no way in YAML to modify previously given values. You cannot override a value given previously with a different one. What you want to do is outside the YAML spec and you would need some merging tool that does such replacements for you.
Related
I have a yaml file which is the start of a Neoload performance test script-as-code;
name: TestFile
servers:
- name: myserver
host: myhost.world.com
scheme: https
I want Jenkins to build the yaml file based on variables from previous steps/pipelines. I have seen writeYaml can do what I want, and it seems fairly easy to use. This is what I have so far;
script{
def map = ['name':'TestFile]
map.put('name','myserver')
writeYaml, file: filename, data: map
}
The def works fine, but I can't quite get a grasp on the syntax to add the 'servers' section to the map object. With what I have above, I just end up with name: myserver in the file.
I did try;
map.put("servers", new String[] {"name","myserver"})
But it doesn't compile (unexpected token "name"), I'm assuming because I'm trying to use the wrong language in a scripted pipeline.
I also appreciate that I could just do the equivalent of 'write line' to the file to generate a YAML, but this file gets a lot more complicated further down so I'd prefer to use this object based approach.
Given an existing Groovy Map with a key-value pair of ['name': 'TestFile], you can add keys and values to this Map in different syntax.
To generate the YAML that you want, you would need an additional key of servers with a one element array (- signifies an Array type in YAML). The member element of that array should be another Map with all of your key value pairs.
You would be looking to append ['servers': [['name': 'myserver', 'host': 'myhost.world.com', 'scheme': 'https']]] to your map. There are three [] in that syntax; the first and third signify Map and the second signifies List. servers is the key in your first Map, and the value is the List. The List's only element is the map of key value pairs. The three key value pairs constitute the map in that List element.
Given two common syntax for appending key value pairs to a Map, you could do either:
map['servers'] = [['name': 'myserver', 'host': 'myhost.world.com', 'scheme': 'https']]
or
map.servers = [['name': 'myserver', 'host': 'myhost.world.com', 'scheme': 'https']]
to achieve the desired result.
I have a feeling this isn't possible, but I have a snippet of YAML that looks like the following:
.map_values: &my_map
a: 'D'
b: 'E'
a: 'F'
section:
stage: *my_map['b']
I would like stage to have the value of E.
Is this possible within YAML? I've tried just about every incarnation of substitution I can think of.
Since there is a duplicate key in your mapping, which is not allowed
in YAML 1.2 (and should at least throw a warning in YAML 1.1) this is
not going to work, but even if you correct that, you can't do that
with just anchors and aliases.
The only substitution like replacement that is available in YAML is the "Merge Key Language-Independent Type". That is indirectly referenced in the YAML spec, and not included in it, but available in most parsers.
The only thing that allows it to do is "update" a mapping with key value pairs of one or more other mappings, if the key doesn't already exist in the mapping. You use the special key << for that, which takes an alias, or a list of aliases.
There is no facility, specified in the YAML specification, to dereference particular keys.
There are some systems that use templates that generate YAML, but there are two main problems to apply these here:
the template languages themselves often are clashing with the indicators in the YAML syntax,
making the template not valid YAML
even if the template could be loaded as valid YAML, and the values extracted that are needed to
update other parts of the template, you would need to parse the input twice (once to get the
values to update the template, then to parse the updated template). Given the potential
complexity of YAML and the relative slow speed of its parsers, this can be prohibitive
What you can do is create some tag (e.g. !lookup) and have its constructor interpret that node.
Since the node has to be valid YAML again you have to decide on whether to use a sequence or a mapping.
You'll have to include some special syntax for the values in both cases, and also for the key
(like the << used in merges) in the case of mappings.
In the examples I left out the spurious single quotes, depending on
your real values you might of course need them.
Example using sequence :
.map_values: &my_map
a: D
b: E
c: F
section: !Lookup
- *my_map
- stage: <b>
Example using mapping:
.map_values: &my_map
a: D
b: E
c: F
section: !Lookup
<<: *my_map
stage: <b>
Both can be made to construct the data on the fly (i.e. no past
loading processing of your data structure necessary). E.g. using Python and
the sequence "style" in input.yaml:
import sys
import ruamel.yaml
from pathlib import Path
input = Path('input.yaml')
yaml = ruamel.yaml.YAML(typ='safe')
yaml.default_flow_style = False
#yaml.register_class
class Lookup:
#classmethod
def from_yaml(cls, constructor, node):
"""
this expects a two entry sequence, in which the first is a mapping X, typically using
an alias
the second entry should be an mapping, for which the values which have the form <key>
are looked up in X
non-existing keys will throw an error during loading.
"""
X, res = constructor.construct_sequence(node, deep=True)
yield res
for key, value in res.items():
try:
if value.startswith('<') and value.endswith('>'):
res[key] = X[value[1:-1]]
except AttributeError:
pass
return res
data = yaml.load(input)
yaml.dump(data, sys.stdout)
which gives:
.map_values:
a: D
b: E
c: F
section:
stage: E
There are a few things to note:
using <...> is arbitrary, you don't need a both beginning and an
end marker. I do recommend using some character(s) that has no
special meaning in YAML, so you don't need to quote your values. You can e.g. use some
well recognisable unicode point, but they tend to be a pain to type in an editor.
when from_yaml is called, the anchor is not yet fully constructed. So X is an empty dict
that gets filled later on. The constructed with yield implements a two step process: we first
give back res "as-is" back to the constructor, then later update it. The constructor stage of
the loader knows how to handle this automatically when it gets the generator instead a "normal" value.
the try .. except is there to handle mapping values that are not strings (i.e. numbers, dates, boolean).
you can do substitutions in keys as well, just make sure you delete the old key
Since tags are standard YAML, the above should be doable one way or another in any
YAML parser, independent of the language.
I have a TinyDB and in each tag of the TinyDB I have a list.
Each list has 3 items, each indexed as 1, 2 and 3.
I want to change the 3rd item, index 3.
So I have done the following
So I want to now save the change in the TinyDB
and have added a storeValue command as follows.
I figured out how to get the valuetoStore variable. As follows.
I had done this before, and thought it wrong because it still doesn't change the 3rd item in the list. But I've added a notifier to look at it and it's correct. So the "replace list item" isn't working how I thought it should. It isn't replacing the 3rd item with an "n."
Any ideas?
Thanks.
Your second try is almost correct. The only thing is, you should use the replace list item block together with the local variable name instead of retrieving the value again from TinyDB.
So what is the difference to your "solution"? Currently you assign the list to a local variable name. Then you use the replace list item block together with a list, you can't store somewhere (you are loading the list again from TinyDB). And in the end you store variable name (which doesn't have been modified at all) in TinyDB. Therefore the solution is to use the replace list item block together with the local variable name instead of retrieving the value again from TinyDB. Btw. a better name for the local variable name would be list.
Further tips
Also in the definition of the local variable name you should add a block, e.g. an empty string or 0
And if you want simplify a little bit, you can move the definition of the local variable name inside the for each loop. And alternatively of using the for each number loop, for list it's easier to use the for each item in list loop, see also the documentation. The list in your case is TinyDB1.GetTags.
As already said in the forum, generally I would use a list of lists and store it in only one tag in TinyDB
How to work with Lists by Saj
How to work with Lists and Lists of lists (pdf) by appinventor.org
I'm using Jammit to package assets up for a Rails application and I have a few asset files that I'd like to be included in each of a few groups. For example, I'd like Sammy and its plugins to be in both my mobile and screen JS packages.
I've tried this:
sammy: &SAMMY
- public/javascripts/vendor/sammy.js
- public/javascripts/vendor/sammy*.js
mobile:
<<: *SAMMY
- public/javascripts/something_else.js
and this:
mobile:
- *SAMMY
but both put the Sammy JS files in a nested Array, which Jammit can't understand. Is there a syntax for including the elements of an Array directly in another Array?
NB: I realize that in this case there are only two elements in the SAMMY Array, so it wouldn't be too bad to give each an alias and reference both in each package. That's fine for this case, but quickly gets unmaintainable when there are five or ten elements that have a specific load order.
Closest solution I know of is this one:
sammy:
- &SAMMY1
public/javascripts/vendor/sammy.js
- &SAMMY2
public/javascripts/vendor/sammy*.js
mobile:
- *SAMMY1
- *SAMMY2
- public/javascripts/something_else.js
Alternatively, as already suggested, flatten the nested lists in a code snippet.
Note: according to yaml-online-parser, your first suggestion is not a valid use of << (used to merge keys from two dictionaries. The anchor then has to point to another dictionary I believe.
If you want mobile to be equal to sammy, you can just do:
mobile: *SAMMY
However if you want mobile to contain other elements in addition to those in sammy, there's no way to do that in YAML to the best of my knowledge.
Your example is valid YAML (a convenient place to check is YPaste), but it's not defined what the merge does. Per the spec, a merge key can have a value:
A mapping, in which case it's merged into the parent mapping.
A sequence of mappings, in which case each is merged, one-by-one, into the parent mapping.
There's no way of merging sequences on YAML level.
You can, however, do this in code. Using the YAML from your second idea:
mobile:
- *SAMMY
you'll get nested sequences - so flatten them! Assuming you have a mapping of such nested sequences:
data = YAML::load(File.open('test.yaml'))
data.each_pair { |key, value| value.flatten! }
(Of course, if you have a more complicated YAML file, and you don't want every sequence flattened (or they're not all sequences), you'll have to do some filtering.)
This solution is for Symfony/PHP only (considerations for other languages, see below)
Note about array keys from the PHP array manual page:
Strings containing valid decimal ints, unless the number is preceded by a + sign, will be cast to the int type. E.g. the key "8" will actually be stored under 8. [...]
This means that if you actually index your anchor array with integer keys, you can simply add new keys by continuing the initial list. So your solution would look like this:
sammy: &SAMMY
1: public/javascripts/vendor/sammy.js
2: public/javascripts/vendor/sammy*.js
mobile:
<<: *SAMMY
3: public/javascripts/something_else.js
You can even overwrite keys and still add new ones:
laptop:
<<: *SAMMY
1: public/javascripts/sammy_laptop.js
3: public/javascripts/something_else.js
In both cases the end result is a perfectly valid indexed array, just like before.
Other programming languages
Depending on your YAML implementation and how you iterate over your array, this could conceivably also be used in other programming languages. Though with a caveat.
For instance, in JS you can access numerical string keys by their integer value as well:
const sammy = {"1": "public/javascripts/vendor/sammy.js"}
sammy["1"]; // "public/javascripts/vendor/sammy.js"
sammy[1]; // "public/javascripts/vendor/sammy.js"
But you'd need to keep in mind, that your initial array is now an object, and that you would need to iterate over it accordingly, e.g.:
Object.keys(sammy).forEach(key => console.log(sammy[key]))
As it has been suggested, when you need to flatten a list, at least in ruby, it is trivial to add a "!flatten" type specifier to mobile and implement a class that extends Array, adds the yaml_tag and flattens the coder seq on init_with.
I'm trying to read files and create a hashmap of the contents, but I'm having trouble at the parsing step. An example of the text file is
put 3
returns 3
between
3
pargraphs 1
4
3
#foo 18
****** 2
The word becomes the key and the number is the value. Notice that the spacing is fairly erratic. The word isn't always a word (which doesn't get picked up by /\w+/) and the number associated with that word isn't always on the same line. This is why I'm calling it not well-formed. If there were one word and one number on one line, I could just split it, but unfortunately, this isn't the case. I'm trying to create a hashmap like this.
{"put"=>3, "#foo"=>18, "returns"=>3, "paragraphs"=>1, "******"=>2, "4"=>3, "between"=>3}
Coming from Java, it's fairly easy. Using Scanner I could just use scanner.next() for the next key and scanner.nextInt() for the number associated with it. I'm not quite sure how to do this in Ruby when it seems I have to use regular expressions for everything.
I'd recommend just using split, as in:
h = Hash[*s.split]
where s is your text (eg s = open('filename').read. Believe it or not, this will give you precisely what you're after.
EDIT: I realized you wanted the values as integers. You can add that as follows:
h.each{|k,v| h[k] = v.to_i}