diagrammatron 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c51dc3c8502bd1626d2a78bccd7e772e60fb25a26e6327879f42144849db598
4
- data.tar.gz: 78548d0ad0af86031afecb81bf4fedca42da58420d0de3a0d5063f07cb68538e
3
+ metadata.gz: 6d1116130a55f28418c67f0522150a80ab39e275e0a473518052758a3b6959c7
4
+ data.tar.gz: 16447cd371aa60d1834356d7f712356400e2ae649124fa46ee6cef683072bc86
5
5
  SHA512:
6
- metadata.gz: 03d48ecd6c7e586007dcb470655fff6814e21d32f1921dcb828b35602bea208e48f58d9ba2ea5cdf5f16fb8f633a242a9d43a599c21fbf61d5ffaa631282c827
7
- data.tar.gz: 5ed263d065a49d69a031fa140ac0159d58301da26f4b51807573e5b8eefbeca6238301f6953cca5c8b44423a83fec18903a7e9d960a5b3fcf1d4702f1b671233
6
+ metadata.gz: 07c0d08e9244fcb9f05910a0513c9efe33e2dab58fa3860d7b168d06e53c8fbda3261aeca58258ec5e00f48568495cec99f5106bedefabe5d04ba59e7b80eaff
7
+ data.tar.gz: fbe5b2147cb128c398bdae0ca4e3afc681683947601d0f2b0034dfa26b253cc26be3328c3c526dcc17ef00f2024e9870dd345f048f093e194ec1b56c1c76f4eb
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2023 Ismo Kärkkäinen
5
+ # Licensed under Universal Permissive License. See LICENSE.txt.
6
+
7
+ require_relative '../lib/common'
8
+ require 'optparse'
9
+
10
+ def make_dst2src(args)
11
+ dst2src = {}
12
+ (0...args.size).step(2) do |sidx|
13
+ src = args[sidx]
14
+ dst = args[sidx + 1]
15
+ dst2src[dst] = src
16
+ end
17
+ dst2src
18
+ end
19
+
20
+ def invert(dst2src)
21
+ src2dst = {}
22
+ dst2src.each do |dst, src|
23
+ src2dst[src] = src2dst.fetch(src, []).push(dst)
24
+ end
25
+ src2dst
26
+ end
27
+
28
+ def gather_values(item, src2dst)
29
+ vals = {}
30
+ src2dst.each_key do |src|
31
+ v = item.fetch(src, nil)
32
+ vals[src] = v unless v.nil?
33
+ end
34
+ vals
35
+ end
36
+
37
+ def copy_fields(item, src2dst)
38
+ vals = gather_values(item, src2dst)
39
+ copied = {}
40
+ vals.each do |src, v|
41
+ src2dst[src].each do |dst|
42
+ copied[dst] = v
43
+ end
44
+ end
45
+ copied
46
+ end
47
+
48
+ def main
49
+ input = nil
50
+ output = nil
51
+ input_output_schema = 'copy'
52
+ ENV['POSIXLY_CORRECT'] = '1' # Leaves field name pairs as they are.
53
+ parser = OptionParser.new do |opts|
54
+ opts.summary_indent = ' '
55
+ opts.summary_width = 20
56
+ opts.banner = 'Usage: diagrammatron-copy [options] source destination ...'
57
+ opts.separator ''
58
+ opts.separator 'Options:'
59
+ opts.on('-i', '--input FILE', 'Input file name. Read from stdin if not given.') do |filename|
60
+ input = filename
61
+ end
62
+ opts.on('-o', '--output FILE', 'Output file name. Write to stdout if not given.') do |filename|
63
+ output = filename
64
+ end
65
+ opts.on('-h', '--help', 'Print this help and exit.') do
66
+ $stdout.puts %(#{opts}
67
+ Source and destination are field names in pairs.
68
+
69
+ Input and output YAML file schema is returned by:
70
+ diagrammatron-schema #{input_output_schema}
71
+
72
+ Output is the input file with fields in nodes and edges copied from source,
73
+ when present, to destination field name. All copies are made at the same
74
+ time, so you can swap field contents, but chaining the copying is not
75
+ supported. In such case, provide the same source field in multiple pairs.
76
+ )
77
+ exit 0
78
+ end
79
+ end
80
+ parser.parse! ARGV
81
+
82
+ return aargh('Field name count is not multiple of 2', 1) if ARGV.size.odd?
83
+
84
+ # Same source can be copied to multiple destinations.
85
+ # Same destination copied to multiple times, last copy remains.
86
+ # Make destination from source mapping and then invert it for actual use.
87
+ src2dst = invert(make_dst2src(ARGV))
88
+
89
+ doc = load_verified(input, input_output_schema)
90
+ return 2 if doc.nil?
91
+
92
+ %w[edges nodes].each do |category|
93
+ items = doc.fetch(category, nil)
94
+ next if items.nil?
95
+ items.each do |item|
96
+ copies = copy_fields(item, src2dst)
97
+ item.merge!(copies)
98
+ end
99
+ end
100
+ save_verified(output, doc, 4, input_output_schema)
101
+ end
102
+
103
+ exit(main) if (defined? $unit_test).nil?
@@ -50,7 +50,7 @@ def main
50
50
  output = filename
51
51
  end
52
52
  opts.on('-l', '--list', 'List available schemas and exit.') do
53
- $stdout.puts list_schemas.join("\n")
53
+ $stdout.puts list_schemas.sort.join("\n")
54
54
  exit 0
55
55
  end
56
56
  opts.on('-h', '--help', 'Print this help and exit.') do
data/lib/copy.yaml ADDED
@@ -0,0 +1,13 @@
1
+ type: object
2
+ properties:
3
+ edges:
4
+ type: array
5
+ minItems: 0
6
+ items:
7
+ type: object
8
+ nodes:
9
+ type: array
10
+ minItems: 0
11
+ items:
12
+ type: object
13
+ additionalProperties: true
data/lib/subsets.rb ADDED
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright © 2023 Ismo Kärkkäinen
4
+ # Licensed under Universal Permissive License. See LICENSE.txt.
5
+
6
+ require_relative '../lib/common'
7
+ require 'ostruct'
8
+ require 'set'
9
+
10
+ def split_expression_string(expression_string)
11
+ out = []
12
+ remaining = expression_string.lstrip
13
+ until remaining.empty?
14
+ item, sep, rest = remaining.partition(/[\s+-]/)
15
+ out << item unless item.empty?
16
+ sep.strip!
17
+ unless sep.empty?
18
+ out << :plus if sep == '+'
19
+ out << :minus if sep == '-'
20
+ end
21
+ remaining = rest.lstrip
22
+ end
23
+ out
24
+ end
25
+
26
+ def identifier?(item)
27
+ item.is_a?(String)
28
+ end
29
+
30
+ def expression_array_errors(expression_array)
31
+ errors = []
32
+ previous_item_type = :symbol
33
+ unless expression_array.empty?
34
+ errors << 'Expression must start with an identifier' unless identifier?(expression_array.first)
35
+ errors << 'Expression must end with an identifier' unless identifier?(expression_array.last)
36
+ end
37
+ expression_array.each_with_index do |item, index|
38
+ current_item_type = identifier?(item) ? :identifier : :symbol
39
+ if current_item_type == previous_item_type
40
+ errors << "Invalid item '#{item}' at index #{index}. Expected #{current_item_type == :identifier ? 'operator' : 'identifier'}."
41
+ end
42
+ previous_item_type = current_item_type
43
+ end
44
+ errors.empty? ? nil : errors
45
+ end
46
+
47
+ def check_rules(r, filename)
48
+ ok = true
49
+ c = {}
50
+ r.fetch('sets', []).each do |setrules|
51
+ name = setrules.delete('name')
52
+ if c.key?(name)
53
+ aargh("#{filename} duplicate set name: #{name}")
54
+ ok = false
55
+ next
56
+ end
57
+ cats = {}
58
+ %i[any nodes edges].each do |category|
59
+ fr = {}
60
+ setrules.fetch(category.to_s, []).each do |fieldrules|
61
+ frn = fieldrules['name']
62
+ res = fr.fetch(frn, [])
63
+ res.concat(fieldrules['rules'].map { |str| Regexp.new(str) })
64
+ fr[frn] = res
65
+ rescue StandardError => e
66
+ aargh("#{filename} #{name} #{category} #{frn} rule error:\n#{e}")
67
+ ok = false
68
+ end
69
+ cats[category] = fr
70
+ end
71
+ c[name] = cats
72
+ end
73
+ r['sets'] = c
74
+ c = {}
75
+ r.fetch('expressions', []).each do |ne|
76
+ name = ne.delete('name')
77
+ if c.key?(name)
78
+ aargh("#{filename} duplicate expression name: #{name}")
79
+ ok = false
80
+ next
81
+ end
82
+ ea = split_expression_string(ne['expression'])
83
+ errs = expression_array_errors(ea)
84
+ unless errs.nil?
85
+ aargh("#{filename} expressions #{name}:\n#{errs.join("\n")}")
86
+ ok = false
87
+ end
88
+ c[name] = ea
89
+ end
90
+ r['expressions'] = c
91
+ ok
92
+ end
93
+
94
+ # Values in any are used if there is no existing value.
95
+ def merge_any(r)
96
+ r.fetch('sets', {}).each_value do |rules|
97
+ any = rules.delete(:any)
98
+ next if any.nil?
99
+ %i[edges nodes].each do |category|
100
+ c = rules.fetch(category, {})
101
+ any.each do |field, patterns|
102
+ next if c.key?(field)
103
+ c[field] = patterns
104
+ end
105
+ rules[category] = c
106
+ end
107
+ end
108
+ end
109
+
110
+ # sets:
111
+ # name:
112
+ # nodes/edges: # Merge here.
113
+ # name: []
114
+ # expressions: # Merge here.
115
+ # name: string
116
+
117
+ def merge_rules(full, overwriting)
118
+ full['expressions'] = {} unless full.key? 'expressions'
119
+ full['expressions'].merge!(overwriting.fetch('expressions', {}))
120
+ sets = full.fetch('sets', {})
121
+ ow = overwriting.fetch('sets', {})
122
+ sets.each do |name, rules|
123
+ %i[nodes edges].each do |category|
124
+ m = ow.dig(name, category)
125
+ next if m.nil?
126
+ rules[category] = {} unless rules.key? category
127
+ rules[category].merge!(m)
128
+ end
129
+ end
130
+ sets.merge!(ow) { |_key, merged, _used| merged } # Adds new sets.
131
+ full['sets'] = sets
132
+ end
133
+
134
+ def match_item(item, rules)
135
+ rules.each do |field, patterns|
136
+ vals = item.fetch(field, nil)
137
+ next if vals.nil?
138
+ vals = [ vals ] unless vals.is_a?(Array)
139
+ patterns.each do |p|
140
+ vals.each do |v|
141
+ next unless v.is_a?(String)
142
+ return true if p.match?(v)
143
+ end
144
+ end
145
+ end
146
+ false
147
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: diagrammatron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismo Kärkkäinen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-19 00:00:00.000000000 Z
11
+ date: 2023-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json_schemer
@@ -36,6 +36,7 @@ description: |2
36
36
  programs that each perform one stage.
37
37
  email: ismokarkkainen@icloud.com
38
38
  executables:
39
+ - diagrammatron-copy
39
40
  - diagrammatron-get
40
41
  - diagrammatron-edges
41
42
  - diagrammatron-nodes
@@ -50,6 +51,7 @@ extensions: []
50
51
  extra_rdoc_files: []
51
52
  files:
52
53
  - LICENSE.txt
54
+ - bin/diagrammatron-copy
53
55
  - bin/diagrammatron-edges
54
56
  - bin/diagrammatron-get
55
57
  - bin/diagrammatron-nodes
@@ -61,11 +63,13 @@ files:
61
63
  - bin/diagrammatron-template
62
64
  - bin/dot_json2diagrammatron
63
65
  - lib/common.rb
66
+ - lib/copy.yaml
64
67
  - lib/edges.yaml
65
68
  - lib/nodes.yaml
66
69
  - lib/place.yaml
67
70
  - lib/render.yaml
68
71
  - lib/subset.yaml
72
+ - lib/subsets.rb
69
73
  - template/internal.yaml
70
74
  - template/root.yaml
71
75
  - template/svg_1.1.erb