Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ arxmodel check --rules autosar.rules.txt <file|dir>... #
arxmodel check --schema-dir DIR --format json <file|dir>... > check.json # or junit: for machines
arxmodel get --schema-dir DIR /Pkg/Name <file|dir>... # the merged element at an AUTOSAR path
arxmodel list --schema-dir DIR ECUC-MODULE-DEF <file|dir>... # the paths of every element of a name
arxmodel values --schema-dir DIR '/Pkg/**/Name' <file|dir>... # every value there, `path = "text"`
arxmodel merge --schema-dir DIR [--force] -o model.arxml <file|dir>... # the merged model, as one ARXML
arxmodel diff A B # what changed, by AUTOSAR path (no schema needed)
arxmodel merge3 BASE OURS THEIRS -o OUT # a three-way merge (no schema needed)
Expand Down Expand Up @@ -351,6 +352,35 @@ scoped rule beside its unscoped counterpart, and a child element no role names,
has rules, gets a rule of the kind no rule gives (each its own) for its place. A merged model is
then written in the order its schema requires.

## Values (`values`)

`values PATTERN <file|dir>...` merges the files as `get` does and prints every value whose path
the pattern matches, or lies below a path it matches, one a line, `path = "text"` (the text a TOML
basic string), in the order of the merged view:

```text
/Pkg/LampCfg/Dim = "10"
/Pkg/LampCfg/Signal = "/Sig/A"
/Pkg/Head#WATTS = "55"
```

A value is an element holding text and no element. One the merge names by its definition (an
item of the policy's `item_identity` roles, or of the built-in rules' `by_definition`: ECUC
parameter and reference values) is named by it: its container's path, `/`, and the definition's
last name, with its variation point's label as a key where it has one (`Dim[v1]`). Of such an
item, its last element, when that holds text only, is the value itself (the value comes last in
such an item); its other parts follow a `#` (`/Pkg/LampCfg/Dim#IS-AUTO-VALUE`). Any other value
is named as findings name elements: its nearest referrable ancestor's path, `#`, and the elements
below it with their keys. A referrable element's SHORT-NAME is its path, not a value; attributes
are not values; what the merge does not compare (structured comments) is left out. Where fragments give a value differently, the first file's is printed, as `merge` writes it
(`check` reports the difference).

The pattern is a path glob, as the policy's filters take them: `*` one segment or part of one,
`**` any segments, `#` matched only by itself or `**`, a keyed segment matched by its name unless
the pattern gives the key. `/Pkg/**/Dim` finds a definition's value wherever it is; `/Pkg/LampCfg`
gives every value of that container and its subcontainers; `/**` every value. A pattern that is a
file or directory is refused (the pattern comes first). Exit 1 when no value matches.

## Export (`merge`)

`merge` merges and checks the files as `check` does, with the same options, the summary going to
Expand Down
1 change: 1 addition & 0 deletions arxmodel/identity.v
Original file line number Diff line number Diff line change
Expand Up @@ -318,5 +318,6 @@ pub fn (x &Identified) flat() Flat {
}
described: ['built-in identity']
unkeyed_by_design: true
identified: x
}
}
12 changes: 9 additions & 3 deletions arxmodel/merge.v
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ pub mut:
unkeyed_by_design bool
// roles whose items are identified by a definition (the policy's item_identity)
identity_roles []IdentityRole
// such roles whose items have no wrapper: keyed by the identity as their split key (values)
direct_identities []IdentityRole
// rules of non-splittable roles allowed in several fragments only as identical repetitions
repeat_rules []int
// elements whose content a merge does not compare (structured comments, by the policy)
ignored_content []string
// the built-in identity rules the Flat was made of, which name values by their definition
// (values); nil for a schema's rules
identified &Identified = unsafe { nil }
}

// Conflict is an element pair under a parent path that roles of different holders still give
Expand Down Expand Up @@ -468,9 +473,10 @@ fn (rs &RuleSet) describe(c Cands, ks []int) []string {
pub fn (rs &RuleSet) to_flat(opts FlatOptions) Flat {
mut c := rs.candidates()
mut f := Flat{
flat_wrappers: c.flats
lost_keys: c.lost_keys
identity_roles: rs.identity_roles()
flat_wrappers: c.flats
lost_keys: c.lost_keys
identity_roles: rs.identity_roles()
direct_identities: rs.direct_identity_roles()
Comment thread
MartenH marked this conversation as resolved.
Resolved
}
if rs.policy.ignore_structured {
f.ignored_content = rs.structured_elements()
Expand Down
47 changes: 41 additions & 6 deletions arxmodel/rules.v
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,43 @@ fn (mut rs RuleSet) check_naming(mut r Rule) {
}
}

// direct_identity_roles are the roles the policy identifies by one role of their items whose items
// are written in their holder with no wrapper: merged by that identity as their split key
// (resolve_key), named by it by values as a wrapper role's items are. Their parent is left empty.
pub fn (rs &RuleSet) direct_identity_roles() []IdentityRole {
mut out := []IdentityRole{}
for r in rs.rules {
if r.name !in rs.policy.item_identity || r.wrapper || r.flat || r.items.len == 0 {
continue
}
def, label := rs.identity_parts(r) or { continue }
out << IdentityRole{
items: r.items.map(it.element)
definition: def
label: label
}
}
return out
}

// identity_parts are the paths of an item_identity role's identity and label in its items' key:
// the part the policy's identity gave (resolve_key), wherever it stands, and the part after it.
fn (rs &RuleSet) identity_parts(r Rule) ?(string, string) {
by := rs.policy.item_identity[r.name] or { return none }
if r.items.len == 0 {
return none
}
w := rs.walk_roles(rs.schema.elements[r.items[0].decl].typ, [by]) or { return none }
def := w.steps.join('/')
key := r.items[0].key
for j, k in key {
if k.steps.join('/') == def {
return def, if j + 1 < key.len { key[j + 1].steps.join('/') } else { '' }
}
}
return none
}

// identity_roles are the wrapper roles the policy identifies by one role of their items: the
// wrapper, its items, and the resolved paths of their identity and of the rest of the key.
pub fn (rs &RuleSet) identity_roles() []IdentityRole {
Expand All @@ -889,15 +926,13 @@ pub fn (rs &RuleSet) identity_roles() []IdentityRole {
if r.name !in rs.policy.item_identity || !r.wrapper || r.flat || r.items.len == 0 {
continue
}
key := r.items[0].key
if key.len == 0 {
continue
}
// the policy's identity wherever it stands in the key, and the part after it
def, label := rs.identity_parts(r) or { continue }
out << IdentityRole{
parent: r.element
items: r.items.map(it.element)
definition: key[0].steps.join('/')
label: if key.len > 1 { key[1].steps.join('/') } else { '' }
definition: def
label: label
}
}
return out
Expand Down
17 changes: 16 additions & 1 deletion arxmodel/ruletext.v
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import os

// rules_format is the version of the rules file's format this arxmodel writes and reads. A file
// of another format is refused: its rules are not in the order this one merges and writes by.
pub const rules_format = 3
pub const rules_format = 4

// version is arxmodel's version, from its own v.mod (builtin.v: @VMOD_FILE would be the v.mod of a
// program that vendors arxmodel).
Expand Down Expand Up @@ -90,6 +90,11 @@ pub fn format_rules(f Flat, pv Provenance, st Settings) !string {
for r in f.identity_roles {
out << '# identity: ${r.parent} ${r.items.join(',')} ${r.definition} ${r.label}'.trim_space()
}
// such roles without a wrapper: their rules key by the definition already; values names their
// items by it
for r in f.direct_identities {
out << '# direct-identity: ${r.items.join(',')} ${r.definition} ${r.label}'.trim_space()
}
// the rules (by their number, from 0) of non-splittable roles that must repeat identically,
// and the elements a merge does not compare
if f.repeat_rules.len > 0 {
Expand Down Expand Up @@ -148,6 +153,16 @@ pub fn parse_rules_file(text string) !(Flat, Provenance, Settings) {
}
}
}
'direct-identity' {
w := value.fields()
if w.len >= 2 {
f.direct_identities << IdentityRole{
items: w[0].split(',')
definition: w[1]
label: if w.len > 2 { w[2] } else { '' }
}
}
}
'conflict' {
pair := value.all_before(' = ')
f.conflicts << Conflict{
Expand Down
72 changes: 72 additions & 0 deletions arxmodel/valuekeys_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,75 @@ fn test_a_rules_file_keeps_the_identity_roles() {
back, _, _ := parse_rules_file(text)!
assert back.identity_roles == flat.identity_roles
}

fn test_values_by_the_schemas_identity_roles() {
p := vk_policy('{ "Shelf.setting" = "definition" }')
// the label after the value (SETTING writes VARIATION-POINT last): the value stays the value
labelled := '<SETTING><DEFINITION-REF>/d/x</DEFINITION-REF><VALUE>1</VALUE><VARIATION-POINT><SHORT-LABEL>v1</SHORT-LABEL></VARIATION-POINT></SETTING>'
m := vk_model('labelled', labelled)
c := check(m, vk_schemas(m, p), skip_xml: true, skip_references: true)!
vals := c.values('/Fiction/**').map(it.line())
assert vals == ['/Fiction/x[v1] = "1"', '/Fiction/x[v1]#VARIATION-POINT/SHORT-LABEL = "v1"'], vals.str()
// each_value gives what values gives, one at a time
mut seen := &[]string{}
n := c.each_value('/Fiction/**', fn [mut seen] (v Value) {
seen << v.line()
})
assert n == 2 && *seen == vals
}

fn test_values_of_a_role_with_no_wrapper() {
// the policy identifies a role written in its holder, with no wrapper, by its definition
dir := os.join_path(os.temp_dir(), 'arxmodel_vk_${os.getpid()}', 'direct')
os.mkdir_all(dir)!
os.write_file(os.join_path(dir, 'a.arxml'), '<?xml version="1.0" encoding="UTF-8"?>
<DEPOT xmlns="urn:arxmodel:test:direct" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:arxmodel:test:direct DIRECT_1.xsd">
<STORES><STORE><SHORT-NAME>Main</SHORT-NAME><OPTION><DEFINITION-REF>/d/x</DEFINITION-REF><VALUE>1</VALUE></OPTION><OPTION><DEFINITION-REF>/d/y</DEFINITION-REF><VALUE>2</VALUE></OPTION></STORE></STORES>
</DEPOT>')!
m := load([dir])!
p := vk_policy('{ "Store.option" = "definition" }')
c := check(m, vk_schemas(m, p), skip_xml: true, skip_references: true)!
assert c.values('/Main/**').map(it.line()) == ['/Main/x = "1"', '/Main/y = "2"']
// through a rules file, the same: the file records the role
sc := vk_schemas(m, p)
flat, _ := governing_flat(sc)!
text := format_rules(flat, provenance(governing_use(sc)!, p), p.settings())!
assert text.contains('\n# direct-identity: OPTION DEFINITION-REF')
g, _, st := parse_rules_file(text)!
assert g.direct_identities.len == 1
d := check_with(m, g, 'rules file', st, p, skip_xml: true, skip_references: true)!
assert d.values('/Main/**').map(it.line()) == ['/Main/x = "1"', '/Main/y = "2"']
}

fn test_values_of_a_role_keyed_by_another_part_first() {
// the split key gives a kind before the item itself: the identity is the definition still
dir := os.join_path(os.temp_dir(), 'arxmodel_vk_${os.getpid()}', 'direct_kind')
os.mkdir_all(dir)!
os.write_file(os.join_path(dir, 'a.arxml'), '<?xml version="1.0" encoding="UTF-8"?>
<DEPOT xmlns="urn:arxmodel:test:direct" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:arxmodel:test:direct DIRECT_1.xsd">
<STORES><STORE><SHORT-NAME>Main</SHORT-NAME><CHOICE><KIND>k</KIND><DEFINITION-REF>/d/z</DEFINITION-REF><VALUE>3</VALUE></CHOICE></STORE></STORES>
</DEPOT>')!
m := load([dir])!
p := vk_policy('{ "Store.choice" = "definition" }')
c := check(m, vk_schemas(m, p), skip_xml: true, skip_references: true)!
vals := c.values('/Main/**').map(it.line())
assert '/Main/z = "3"' in vals, vals.str()
assert !vals.any(it.starts_with('/Main/k')), vals.str()
}

fn test_values_of_an_attribute_identity_and_a_wrapper_keyed_by_another_part_first() {
dir := os.join_path(os.temp_dir(), 'arxmodel_vk_${os.getpid()}', 'direct_attr')
os.mkdir_all(dir)!
os.write_file(os.join_path(dir, 'a.arxml'), '<?xml version="1.0" encoding="UTF-8"?>
<DEPOT xmlns="urn:arxmodel:test:direct" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:arxmodel:test:direct DIRECT_1.xsd">
<STORES><STORE><SHORT-NAME>Main</SHORT-NAME><FLAG NAME="/f/on"><VALUE>yes</VALUE></FLAG><ENTRIES><ENTRY><KIND>k</KIND><DEFINITION-REF>/d/w</DEFINITION-REF><VALUE>4</VALUE></ENTRY></ENTRIES></STORE></STORES>
</DEPOT>')!
m := load([dir])!
p := vk_policy('{ "Store.flag" = "name", "Store.entry" = "definition" }')
c := check(m, vk_schemas(m, p), skip_xml: true, skip_references: true)!
vals := c.values('/Main/**').map(it.line())
// an identity read from an attribute; a wrapper role named by its definition, not its kind
assert '/Main/on = "yes"' in vals, vals.str()
assert '/Main/w = "4"' in vals, vals.str()
assert !vals.any(it.starts_with('/Main/k')), vals.str()
}
Loading
Loading