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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Gemfile.lock +7 -1
- data/README.md +14 -0
- data/bin/console +1 -0
- data/dgd-tools.gemspec +3 -0
- data/example_xml/CraftDaemon.xml +42 -0
- data/example_xml/Materials.xml +3 -0
- data/example_xml/PropertyTypes.xml +49 -0
- data/example_xml/Thing.xml +41 -0
- data/example_xml/Thing2.xml +42 -0
- data/example_xml/t1/Thing.xml +41 -0
- data/example_xml/t2/Thing.xml +42 -0
- data/exe/dgd-manifest +40 -1
- data/exe/skotos-xml-diff +45 -0
- data/goods/chattheatre_kernellib.goods +12 -0
- data/lib/dgd-tools/manifest.rb +375 -88
- data/lib/dgd-tools/skotos_xml_obj.rb +283 -0
- data/lib/dgd-tools/version.rb +1 -1
- metadata +42 -3
@@ -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
|
data/lib/dgd-tools/version.rb
CHANGED
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.
|
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:
|
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:
|