dgd-tools 0.1.2 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: