dgd-tools 0.1.2 → 0.1.7

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.
@@ -0,0 +1,283 @@
1
+ require "dgd-tools/version"
2
+
3
+ # Nokogiri is unusually permissive as an XML parser, which is
4
+ # good - SkotOS XML objects don't parse with most XML parsers.
5
+ require "nokogiri"
6
+
7
+ require "tempfile"
8
+
9
+ module SkotOS; end
10
+
11
+ class SkotOS::XMLObject; end
12
+
13
+ class << SkotOS::XMLObject
14
+ attr_accessor :merry_only
15
+ attr_accessor :ignore_whitespace
16
+ attr_accessor :ignore_types
17
+ end
18
+
19
+ class SkotOS::XMLObject
20
+ attr_reader :pretty
21
+ attr_reader :noko_doc
22
+
23
+ def initialize(pretty, noko_doc: nil)
24
+ @pretty = pretty
25
+ @noko_doc = noko_doc
26
+ end
27
+
28
+ def self.from_file(filename)
29
+ # SkotOS files often have references to undefined namespaces,
30
+ # but we can get Nokogiri to parse it.
31
+ doc = Nokogiri::XML(File.read filename)
32
+
33
+ remove_undiffed(doc)
34
+
35
+ pretty = doc.to_xml(indent:3)
36
+ SkotOS::XMLObject.new pretty, noko_doc: doc
37
+ end
38
+
39
+ def self.diff_between(obj1, obj2, o1_name: "Object 1", o2_name: "Object 2")
40
+ of1 = Tempfile.new("skotos_xml_diff1_")
41
+ of2 = Tempfile.new("skotos_xml_diff2_")
42
+
43
+ begin
44
+ of1.write(obj1.pretty)
45
+ of2.write(obj2.pretty)
46
+ of1.close
47
+ of2.close
48
+
49
+ diff_opts = [ "-c" ]
50
+ diff_opts += [ "-w", "-B" ] if self.ignore_whitespace
51
+
52
+ # Diff 'fails' if there's a difference between the two files.
53
+ diff = system_call("diff #{diff_opts.join(" ")} #{of1.path} #{of2.path}", fail_ok: true)
54
+ diff.sub!(of1.path, o1_name)
55
+ diff.sub!(of2.path, o2_name)
56
+ ensure
57
+ of1.unlink
58
+ of2.unlink
59
+ end
60
+ diff
61
+ end
62
+
63
+ def self.skip_ignored_files(list, base_dir)
64
+ if self.merry_only
65
+ list.select { |path| File.directory?(base_dir + "/" + path) ||
66
+ path[/.xml$/] || path[/.XML$/] }
67
+ else
68
+ list.select do |path|
69
+ !path[/,v$/] && # Ignore files ending in comma-v
70
+ !path[/-backup-\d+-\d+-\d+\.xml/] && # Ignore files ending in -backup-[DATE].xml
71
+ path != ".git" && # Ignore .git directories
72
+ path != "MOVED" # Ignore MOVED - it's a sort of recycle, waiting to be emptied
73
+ end
74
+ end
75
+ end
76
+
77
+ def self.diff_dirs(dir1, dir2)
78
+ entries1 = skip_ignored_files(Dir.glob("*", base: dir1).to_a, dir1)
79
+ entries2 = skip_ignored_files(Dir.glob("*", base: dir2).to_a, dir2)
80
+
81
+ only_in_1 = entries1 - entries2
82
+ only_in_2 = entries2 - entries1
83
+ in_both = entries1 & entries2
84
+
85
+ diff = []
86
+ diff << "Only in first: #{only_in_1.map { |s| dir1 + "/" + s }.join(", ")}" unless only_in_1.empty?
87
+ diff << "Only in second: #{only_in_2.map { |s| dir2 + "/" + s }.join(", ")}" unless only_in_2.empty?
88
+
89
+ in_both.each do |file|
90
+ in_1 = File.join dir1, file
91
+ in_2 = File.join dir2, file
92
+ if File.directory?(in_1) ^ File.directory?(in_2)
93
+ diff << "Only a directory in one, not both: #{dir1}/#{file}"
94
+ elsif File.directory?(in_1)
95
+ d = diff_dirs(in_1, in_2)
96
+ diff.concat(d)
97
+ else
98
+ o1 = from_file(in_1)
99
+ o2 = from_file(in_2)
100
+ this_diff = diff_between(o1, o2, o1_name: in_1, o2_name: in_2)
101
+ diff << this_diff unless this_diff.strip == ""
102
+ end
103
+ end
104
+ diff
105
+ end
106
+
107
+ def self.remove_undiffed(doc)
108
+ if doc.root && doc.root.element?
109
+ ignored_top_elements = ["program", "clone", "owner"]
110
+ ignored_top_elements.each do |attr|
111
+ if doc.root.attribute(attr)
112
+ doc.root.remove_attribute(attr)
113
+ end
114
+ end
115
+ end
116
+
117
+ rev = noko_single_node(doc.root, "Core:Property", attrs: { "property" => "revisions" })
118
+ rev.remove if rev
119
+
120
+ list = noko_single_node(doc.root, "Core:Property", attrs: { "property" => "#list#" })
121
+ list.remove if list
122
+
123
+ if self.merry_only
124
+ # Kill off all the non-Merry nodes
125
+ noko_remove_non_merry_nodes(doc.root)
126
+ end
127
+
128
+ if self.ignore_types
129
+ self.ignore_types.each do |ignored_type|
130
+ skipped = noko_with_name_and_attrs(doc.root, ignored_type)
131
+ skipped.each { |n| n.remove }
132
+ end
133
+ end
134
+
135
+ base_combat = noko_single_node(doc.root, "Base:Combat")
136
+ if base_combat
137
+ base_strength = noko_single_node(base_combat, "Base:Strength", attrs: { "value" => "1" })
138
+ base_max_fatigue = noko_single_node(base_combat, "Base:MaxFatigue", attrs: { "value" => "1" })
139
+ if base_strength && base_max_fatigue && noko_non_text(base_combat.children).size == 2
140
+ next_text = base_combat.next
141
+ base_combat.remove
142
+ next_text.remove
143
+ end
144
+ end
145
+ end
146
+
147
+ def self.noko_single_node(node, name, attrs: {})
148
+ choices = noko_with_name_and_attrs(node, name, attrs)
149
+ if choices.size < 1
150
+ nil
151
+ elsif choices.size > 1
152
+ raise "Single-node search returned more than one node! #{name.inspect}, #{attrs.inspect}"
153
+ else
154
+ choices[0]
155
+ end
156
+ end
157
+
158
+ def self.noko_non_text(nodes)
159
+ nodes.select { |n| !n.is_a? Nokogiri::XML::Text }
160
+ end
161
+
162
+ def self.noko_with_name_and_attrs(node, name, attrs = {})
163
+ results = node.children.flat_map { |n| noko_with_name_and_attrs(n, name, attrs) }
164
+ if node.name == name &&
165
+ attrs.all? { |k, v| node.attribute(k).value == v }
166
+ results << node
167
+ end
168
+ results
169
+ end
170
+
171
+ def self.noko_remove_non_merry_nodes(root)
172
+ root.children.each do |node|
173
+ if node.name != "Core:PropertyContainer"
174
+ node.remove
175
+ next
176
+ end
177
+
178
+ node.children.each do |node2|
179
+ if node2.name != "Core:PCProperties"
180
+ node2.remove
181
+ next
182
+ end
183
+
184
+ node2.children.each do |property_node|
185
+ if property_node.name != "Core:Property" || property_node.attribute("property").value[0..5] != "merry:"
186
+ property_node.remove
187
+ next
188
+ end
189
+ # Leave the Merry node alone
190
+ end
191
+
192
+ if node2.children.size == 0
193
+ node2.remove
194
+ end
195
+ end
196
+ if node.children.size == 0
197
+ node.remove
198
+ end
199
+ end
200
+ end
201
+
202
+ def self.system_call(cmd, fail_ok: false)
203
+ f = Tempfile.new("system_call_xml_diff_")
204
+ begin
205
+ system(cmd, out: f)
206
+ unless fail_ok || $?.success?
207
+ f.rewind
208
+ out = f.read
209
+ raise "Error running command: #{cmd.inspect}!\n\nOutput:\n#{out}\n\n"
210
+ end
211
+ f.rewind
212
+ return f.read
213
+ ensure
214
+ f.close
215
+ f.unlink
216
+ end
217
+ end
218
+ end
219
+
220
+ =begin
221
+ # Abandoned approach follows
222
+ # Some code taken from: https://stackoverflow.com/a/10144623
223
+ class Nokogiri::XML::Node
224
+ TYPENAMES = {1=>'element',2=>'attribute',3=>'plaintext',4=>'cdata',8=>'comment'}
225
+ def to_hash
226
+ {kind:TYPENAMES[node_type],name:name}.tap do |h|
227
+ h.merge! nshref:namespace.href, nsprefix:namespace.prefix if namespace
228
+ h.merge! text:text
229
+ h.merge! attr:attribute_nodes.map(&:to_hash) if element?
230
+ h.merge! kids:children.map(&:to_hash) if element?
231
+ end
232
+ end
233
+ end
234
+ class Nokogiri::XML::Document
235
+ def to_hash; root.to_hash; end
236
+ end
237
+
238
+ class SkotOS::XMLObject
239
+ OBJ_FIELDS = [:kind, :name, :text, :attr, :nshref, :nsprefix]
240
+ def self.diff_between(obj1, obj2, diff = [])
241
+ single_obj1 = obj1.slice(*OBJ_FIELDS)
242
+ single_obj2 = obj2.slice(*OBJ_FIELDS)
243
+
244
+ this_diff = []
245
+ OBJ_FIELDS.each do |field|
246
+ if single_obj1[field] != single_obj2[field]
247
+ this_diff.concat ["+#{field}: #{single_obj2[field]}", "-#{field}: #{single_obj1[field]}"]
248
+ end
249
+ end
250
+
251
+ single_obj1[:kids]
252
+
253
+ diff
254
+ end
255
+
256
+ def self.prune_whitespace(data)
257
+ data[:text].gsub!(/\W+/, " ")
258
+ data[:text].strip!
259
+ new_kids = data[:kids].flat_map do |node|
260
+ if node[:kind] == "comment"
261
+ []
262
+ elsif node[:kind] == "plaintext"
263
+ new_text = node[:text].gsub(/\W+/, " ").strip
264
+ if new_text == ""
265
+ []
266
+ else
267
+ node[:text] = new_text
268
+ [node]
269
+ end
270
+ elsif node[:kind] == "element" || node[:kind] == "attribute"
271
+ node[:text].gsub!(/\W+/, " ")
272
+ node[:text].strip!
273
+ prune_whitespace(node)
274
+ [node]
275
+ else
276
+ raise "Is this illegal or did I just not anticipate it?"
277
+ end
278
+ end
279
+ data[:kids] = new_kids
280
+ nil
281
+ end
282
+ end
283
+ =end
@@ -1,3 +1,3 @@
1
1
  module DGD
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.7"
3
3
  end
metadata CHANGED
@@ -1,21 +1,50 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dgd-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Gibbs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-23 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2021-03-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.10.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.10.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: optimist
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.1
13
41
  description: dgd-tools supplies DGD Manifest and eventually perhaps other tools. DGD
14
42
  Manifest is an experimental DGD library and packaging system.
15
43
  email:
16
44
  - the.codefolio.guy@gmail.com
17
45
  executables:
18
46
  - dgd-manifest
47
+ - skotos-xml-diff
19
48
  extensions: []
20
49
  extra_rdoc_files: []
21
50
  files:
@@ -29,9 +58,19 @@ files:
29
58
  - bin/console
30
59
  - bin/setup
31
60
  - dgd-tools.gemspec
61
+ - example_xml/CraftDaemon.xml
62
+ - example_xml/Materials.xml
63
+ - example_xml/PropertyTypes.xml
64
+ - example_xml/Thing.xml
65
+ - example_xml/Thing2.xml
66
+ - example_xml/t1/Thing.xml
67
+ - example_xml/t2/Thing.xml
32
68
  - exe/dgd-manifest
69
+ - exe/skotos-xml-diff
70
+ - goods/chattheatre_kernellib.goods
33
71
  - goods/skotos_httpd.goods
34
72
  - lib/dgd-tools/manifest.rb
73
+ - lib/dgd-tools/skotos_xml_obj.rb
35
74
  - lib/dgd-tools/version.rb
36
75
  homepage: https://github.com/noahgibbs/dgd-tools
37
76
  licenses: