dgd-tools 0.1.3 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bedae7ff16304641a20a9fe56eb1e6113bfe57641e6deedc05f23068303426b
4
- data.tar.gz: 7d9956ee4dee5c1893e39d95a965e1ecc28ae5bda10cfb9b61c6cb0793174675
3
+ metadata.gz: d608eb777e5e4e39c2d9e08175876de2b7a47f8fc20b775692165d8c46dcd3cd
4
+ data.tar.gz: c4a833c253ac5c8c191f5d288ae30b3bcb4301371940e524ccd34ce219163f3a
5
5
  SHA512:
6
- metadata.gz: 81163aa4aca58ce6e5231c951244d74f9e979558d9164883ebc3ce11069c700b4ea15da3adca85cf668d053473060a721cfd56b3dc6830d22ab307ccf37d4678
7
- data.tar.gz: c7bc8985bddaf46ca782ef9be6e84472b8f783c3edaa5477c9602975fb4b58aee8869b883582350fab613f53140bb91fd9d757354ae13d6231c127bf8c6b835d
6
+ metadata.gz: c107d368ef11e8742dcb024194bfb1b13ff916d1572589a0adee3ca263618e56eaed34f407f611e175e0332a7591d4ed1d0de6b2fa624c714c4680669ac84769
7
+ data.tar.gz: 66adf5b5c678361e47ae5dac4d706cabeec768de2b50ff021cb101e2ec008d6fa18a39f567e3617272250240c9c9bab32da50d423397d8ab9064ef2fb6d4f448
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  *.gem
10
+ test/data/**/dgd.config
11
+ test/data/*/.root
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dgd-tools (0.1.2)
4
+ dgd-tools (0.1.8)
5
5
  nokogiri (~> 1.10.5)
6
+ optimist (~> 3.0.1)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
@@ -11,6 +12,7 @@ GEM
11
12
  minitest (5.14.2)
12
13
  nokogiri (1.10.10)
13
14
  mini_portile2 (~> 2.4.0)
15
+ optimist (3.0.1)
14
16
  rake (12.3.3)
15
17
 
16
18
  PLATFORMS
data/README.md CHANGED
@@ -14,6 +14,18 @@ DGD Manifest is a simple initial library system. I'm sure I'll figure more out a
14
14
 
15
15
  This work has grown out of [SkotOS and ChatTheatre](https://github.com/ChatTheatre) tasks.
16
16
 
17
+ You can find example DGD manifest files under the "test" directory and also in [various](https://github.com/noahgibbs/prototype_vRWOT) [SkotOS-based games](https://github.com/ChatTheatre/gables_game) that use the DGD Manifest system.
18
+
19
+ You can find example "goods" (library) files under the "goods" subdirectory of this repo.
20
+
21
+ ## WOE Objects and skotos-xml-diff
22
+
23
+ SkotOS-based games use an XML format for in-game objects called WOE, which is [documented in SkotOS-Doc](https://ChatTheatre.github.io/SkotOS-Doc). The skotos-xml-diff utility will diff between WOE objects or directories of WOE objects.
24
+
25
+ See SkotOS-Doc for more detail about how this can be used with a SkotOS game.
26
+
27
+ Run "skotos-xml-diff --help" for a list of options. You can tell it to ignore whitespace, to diff only the Merry (script) contents of the objects, and to ignore certain XML node types.
28
+
17
29
  ## Installation
18
30
 
19
31
  You would normally install DGDTools directly: `gem install dgd-tools`.
@@ -26,6 +38,8 @@ If you have a DGD application that uses DGD Manifest, run `dgd-manifest install`
26
38
 
27
39
  That fully-assembled DGD directory is named ".root". To run your dgd server, type "dgd-manifest server".
28
40
 
41
+ If you update files in your root and want to update files under the generated root directory, use "dgd-manifest update".
42
+
29
43
  ## Using DGD Manifest with your DGD Application
30
44
 
31
45
  Your app will need a dgd.manifest file, which is a lot like NPM's package.json file.
data/dgd-tools.gemspec CHANGED
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.require_paths = ["lib"]
26
26
 
27
27
  spec.add_runtime_dependency "nokogiri", "~>1.10.5"
28
+ spec.add_runtime_dependency "optimist", "~>3.0.1"
28
29
  end
@@ -0,0 +1,41 @@
1
+ <object program="/base/obj/thing">
2
+ <Base:Thing>
3
+ <Ur:UrObject/>
4
+ <Base:Bulk immobile="false" mass="1" density="1"/>
5
+ <Base:Container flexible="false" transparent-container="false" public-container="false" tight="false" capacity="0" maxweight="0"/>
6
+ <Base:Misc gender="neuter" volition="false" weapon="false" default_stance="none" combinable="false" discrete="false" by_weight="false" tight="false" scriptrunner="false">
7
+ <Base:Edible value="false"/>
8
+ <Base:Potable value="false"/>
9
+ <Base:DrinkMessageFirst/>
10
+ <Base:DrinkMessageThird/>
11
+ <Base:Transparency value="false"/>
12
+ <Base:Unsafe value="false"/>
13
+ <Base:Safe value="false"/>
14
+ <Base:ClothesExpected value="false"/>
15
+ <Base:DieMessageFirst/>
16
+ <Base:DieMessageThird/>
17
+ </Base:Misc>
18
+ <Base:Details/>
19
+ <Base:Combat>
20
+ <Base:Strength value="1"/>
21
+ <Base:MaxFatigue value="1"/>
22
+ </Base:Combat>
23
+ <Base:Clothing>
24
+ <Base:SingleWear value="false"/>
25
+ </Base:Clothing>
26
+ <Base:Crafting see_level="0" do_level="0" time="0" attention="false" held="false">
27
+ <Base:Ingredients/>
28
+ <Base:Tools/>
29
+ <Base:CraftVerbs/>
30
+ </Base:Crafting>
31
+ <Base:InitialContents/>
32
+ <Base:InitialProperties/>
33
+ <Core:Properties>
34
+ <Core:Property property="revisions">
35
+ (\{ 1065557891, "the_exiled", "E" \})
36
+ </Core:Property>
37
+ <Core:Property property="skill:fatigue">1.0</Core:Property>
38
+ </Core:Properties>
39
+ <Notes:Notes/>
40
+ </Base:Thing>
41
+ </object>
@@ -0,0 +1,42 @@
1
+ <object program="/base/obj/thing" owner="bobo">
2
+ <Base:Thing>
3
+ <Ur:UrObject/>
4
+ <Base:Bulk immobile="false" mass="1" density="1"/>
5
+ <Base:Container flexible="false" transparent-container="false" public-container="false" tight="false" capacity="0" maxweight="0"/>
6
+ <Base:Misc gender="neuter" volition="false" weapon="false" default_stance="none" combinable="false" discrete="false" by_weight="false" tight="false" scriptrunner="false" bogusfield="nope">
7
+ <Base:Edible value="false"/>
8
+ <Base:Potable value="false"/>
9
+ <Base:DrinkMessageFirst/>
10
+ <Base:DrinkMessageThird/>
11
+ <Base:Transparency value="false"/>
12
+ <Base:Unsafe value="false"/>
13
+ <Base:Safe value="false"/>
14
+ <Base:ClothesExpected value="false"/>
15
+ <Base:DieMessageFirst/>
16
+ <Base:DieMessageThird/>
17
+ <Base:DieMessageFortySecond/>
18
+ </Base:Misc>
19
+ <Base:Details/>
20
+ <Base:Combat>
21
+ <Base:Strength value="1"/>
22
+ <Base:MaxFatigue value="1"/>
23
+ </Base:Combat>
24
+ <Base:Clothing>
25
+ <Base:SingleWear value="false"/>
26
+ </Base:Clothing>
27
+ <Base:Crafting see_level="0" do_level="0" time="0" attention="false" held="false">
28
+ <Base:Ingredients/>
29
+ <Base:Tools/>
30
+ <Base:CraftVerbs/>
31
+ </Base:Crafting>
32
+ <Base:InitialContents/>
33
+ <Base:InitialProperties/>
34
+ <Core:Properties>
35
+ <Core:Property property="revisions">
36
+ (\{ 1065557891, "the_exiled", "E" \})
37
+ </Core:Property>
38
+ <Core:Property property="skill:fatigue">1.0</Core:Property>
39
+ </Core:Properties>
40
+ <Notes:Notes/>
41
+ </Base:Thing>
42
+ </object>
data/exe/dgd-manifest CHANGED
@@ -1,35 +1,82 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require "optimist"
3
4
  require "dgd-tools/manifest"
4
5
 
5
- if ARGV.size == 0
6
- ARGV.push "install"
6
+ SUB_COMMANDS = %w(new test install update server)
7
+
8
+ OPTS = Optimist::options do
9
+ version "DGD-tools version #{DGD::VERSION}"
10
+ banner <<BANNER
11
+ Use dgd.manifest to assemble your DGD application.
12
+
13
+ Available subcommands:
14
+ new [project_name]: create a new DGD-manifest project
15
+ test: make sure the dgd.manifest file is well-formed and usable
16
+ install: compile the DGD application to a config file and a root directory
17
+ update: copy files into generated root directory but do *not* clear 'extra' files (e.g. user data)
18
+ server: run DGD with the generated root and configuration
19
+
20
+ Available options:
21
+ BANNER
22
+ opt :verbose, "Print verbose output where available"
23
+ stop_on SUB_COMMANDS
7
24
  end
8
25
 
9
- case ARGV[0]
10
- when "test"
11
- unless File.exist?("dgd.manifest")
12
- raise "I don't see a dgd.manifest file in this directory!"
13
- end
14
- puts "Running dgd.manifest installer..."
15
- repo = DGD::Manifest::Repo.new
16
- repo.manifest_file("dgd.manifest")
17
- repo.precheck(".")
18
- puts "Verified Manifest packages: this looks likely correct."
19
- when "install"
20
- unless File.exist?("dgd.manifest")
21
- raise "I don't see a dgd.manifest file in this directory!"
26
+ ARGV.push("install") if ARGV.size == 0
27
+ cmd = ARGV.shift
28
+ cmd_opts = case cmd
29
+ when "test"
30
+ #Optimist::options do
31
+ # opt :harsh, "check as exactly as possible"
32
+ #end
33
+
34
+ unless File.exist?("dgd.manifest")
35
+ raise "I don't see a dgd.manifest file in this directory!"
36
+ end
37
+ puts "Running dgd.manifest installer..."
38
+ repo = DGD::Manifest::Repo.new
39
+ repo.manifest_file("dgd.manifest")
40
+ repo.precheck(".", verbose: verbose)
41
+ puts "Verified Manifest packages: this looks likely correct."
42
+
43
+ when "install"
44
+ unless File.exist?("dgd.manifest")
45
+ raise "I don't see a dgd.manifest file in this directory!"
46
+ end
47
+ puts "Running DGD Manifest installer..."
48
+ repo = DGD::Manifest::Repo.new
49
+ repo.manifest_file("dgd.manifest")
50
+ current_dir = File.expand_path(".")
51
+ repo.precheck(current_dir)
52
+ repo.assemble_app(current_dir, verbose: OPTS[:verbose])
53
+ puts "Assembled DGD application into #{current_dir}"
54
+
55
+ when "update"
56
+ unless File.exist?("dgd.manifest")
57
+ raise "I don't see a dgd.manifest file in this directory!"
58
+ end
59
+ puts "Running DGD Manifest installer..."
60
+ repo = DGD::Manifest::Repo.new
61
+ repo.manifest_file("dgd.manifest")
62
+ current_dir = File.expand_path(".")
63
+ repo.precheck(current_dir, verbose: OPTS[:verbose])
64
+ repo.update_app(current_dir, verbose: OPTS[:verbose])
65
+ puts "Updated DGD application in #{current_dir}"
66
+
67
+ when "server"
68
+ puts "Starting DGD server..."
69
+ DGD::Manifest.system_call("~/.dgd-tools/dgd/bin/dgd dgd.config")
70
+
71
+ when "new"
72
+ unless ARGV.size == 1
73
+ puts "Usage: dgd-manifest new [project name]"
74
+ Optimist::die "Must supply exactly one argument to dgd-manifest new!"
75
+ end
76
+ appdir = DGD::Manifest::AppDirectory.new(File.expand_path ARGV[0])
77
+ appdir.name = ARGV[0]
78
+ appdir.create!
79
+
80
+ else
81
+ Optimist::die "Unknown subcommand: #{cmd.inspect}"
22
82
  end
23
- puts "Running DGD Manifest installer..."
24
- repo = DGD::Manifest::Repo.new
25
- repo.manifest_file("dgd.manifest")
26
- current_dir = File.expand_path(".")
27
- repo.precheck(current_dir)
28
- repo.assemble_app(current_dir)
29
- puts "Assembled DGD application into #{current_dir}"
30
- when "server"
31
- puts "Starting DGD server..."
32
- DGD::Manifest.system_call("~/.dgd-tools/dgd/bin/dgd dgd.config")
33
- else
34
- raise "Unrecognised #{$0} command: #{ARGV[0].inspect}!"
35
- end
data/exe/skotos-xml-diff CHANGED
@@ -4,17 +4,28 @@
4
4
  # SkotOS-or-similar game to find out what updates should
5
5
  # be merged between them.
6
6
 
7
+ require 'optimist'
7
8
  require "dgd-tools/skotos_xml_obj"
8
9
 
9
- if ARGV.size != 2
10
- STDERR.puts "Usage: skotos-xml-diff <obj_or_dir_1> <obj_or_dir_2>"
11
- exit -1
10
+ OPTS = Optimist::options do
11
+ version "DGD-tools version #{DGD::VERSION}"
12
+ banner <<BANNER
13
+ Usage:
14
+ skotos-xml-diff [options] <file_or_dir_1> <file_or_dir_2>
15
+ where [options] are:
16
+ BANNER
17
+ opt :version, "Print DGD version and exit"
18
+ opt :merry_only, "Only diff Merry scripts"
19
+ opt :ignore_whitespace, "Ignore whitespace in final diff"
20
+ opt :ignore_types, "Comma-separated list of XML node types to ignore (e.g. Combat:Base,Base:Exit)", type: :string
12
21
  end
13
22
 
14
- if File.directory?(ARGV[0]) ^ File.directory?(ARGV[1])
15
- STDERR.puts "Please give two files or two directories, not one of each!"
16
- exit -1
17
- end
23
+ Optimist::die "Supply exactly two files or directories -- you supplied #{ARGV.size}!" unless ARGV.size == 2
24
+ Optimist::die "Supply both files or both directories, not one of each!" if File.directory?(ARGV[0]) ^ File.directory?(ARGV[1])
25
+
26
+ SkotOS::XMLObject.merry_only = true if OPTS[:merry_only]
27
+ SkotOS::XMLObject.ignore_whitespace = true if OPTS[:ignore_whitespace]
28
+ SkotOS::XMLObject.ignore_types = OPTS[:ignore_types].split(",") if OPTS[:ignore_types]
18
29
 
19
30
  if File.directory?(ARGV[0])
20
31
  diff = SkotOS::XMLObject.diff_dirs(*ARGV)
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "ChatTheatre Kernellib",
3
+ "git": {
4
+ "url": "https://github.com/ChatTheatre/kernellib.git",
5
+ "branch": "master"
6
+ },
7
+ "paths": {
8
+ "src/include/kernel": "include/kernel",
9
+ "src/include/*.h": "include",
10
+ "src/kernel": "kernel"
11
+ }
12
+ }
@@ -16,38 +16,39 @@ module DGD::Manifest
16
16
  }
17
17
  KERNEL_PATHS = KERNEL_PATH_MAP.values
18
18
  DEFAULT_KERNELLIB_URL = "https://github.com/ChatTheatre/kernellib"
19
-
20
19
  GENERATED_ROOT = ".root"
21
20
 
22
21
  def self.system_call(cmd)
23
22
  puts "Running command: #{cmd.inspect}..."
24
23
  system(cmd, out: $stdout, err: :out)
25
24
  unless $?.success?
26
- raise "Error running command: #{cmd.inspect}!"
25
+ raise "Error running command in #{Dir.pwd}: #{cmd.inspect}!"
27
26
  end
28
27
  end
29
28
 
30
29
  # This is a repo of everything DGD Manifest saves between runs.
31
30
  # It includes downloaded Git repos, Goods files and more.
32
31
  class Repo
33
- attr_reader :manifest_dir
32
+ attr_reader :shared_dir
34
33
 
35
34
  def initialize
35
+ @no_manifest_file = true
36
36
  @home = ENV["HOME"]
37
- @manifest_dir = "#{@home}/.dgd-tools"
38
- Dir.mkdir(@manifest_dir) unless File.directory?(@manifest_dir)
37
+ @shared_dir = "#{@home}/.dgd-tools"
38
+ Dir.mkdir(@shared_dir) unless File.directory?(@shared_dir)
39
+
39
40
  ["git", "goods"].each do |subdir|
40
- full_subdir = "#{@manifest_dir}/#{subdir}"
41
+ full_subdir = "#{@shared_dir}/#{subdir}"
41
42
  Dir.mkdir(full_subdir) unless File.directory?(full_subdir)
42
43
  end
43
44
 
44
- unless File.exist?("#{@manifest_dir}/dgd/bin/dgd")
45
- dgd_dir = "#{@manifest_dir}/dgd"
45
+ unless File.exist?("#{@shared_dir}/dgd/bin/dgd")
46
+ dgd_dir = "#{@shared_dir}/dgd"
46
47
  if File.directory?(dgd_dir)
47
48
  # Not clear to me what to do here...
48
49
  else
49
50
  DGD::Manifest.system_call("git clone https://github.com/ChatTheatre/dgd.git #{dgd_dir}")
50
- Dir.chdir("#{@manifest_dir}/dgd/src") do
51
+ Dir.chdir("#{@shared_dir}/dgd/src") do
51
52
  DGD::Manifest.system_call(DGD_BUILD_COMMAND)
52
53
  end
53
54
  end
@@ -60,82 +61,120 @@ module DGD::Manifest
60
61
  end
61
62
 
62
63
  def manifest_file(path)
63
- raise "Already have a dgd.manifest file!" if @manifest_file
64
+ raise "Already have a dgd.manifest file!" unless @no_manifest_file
64
65
 
65
- @manifest_file ||= AppFile.new(self, path)
66
+ @no_manifest_file = false
67
+ @manifest_file ||= AppFile.new(self, path, shared_dir: shared_dir)
66
68
  end
67
69
 
68
- def files_to_assemble
69
- subdirs = []
70
+ protected
71
+
72
+ # This includes files to assemble... But also subdirectories and commands. This format is
73
+ # unstable and ugly, and should not be exposed to outside parties who might later depend on it.
74
+ def assembly_operations(location, verbose:)
75
+ operations = []
70
76
 
71
- @manifest_file.specs.each do |spec|
72
- git_repo = spec.source
73
- git_repo.use_details(spec.source_details)
77
+ raise("No manifest file!") if @no_manifest_file
78
+
79
+ # For each spec, put its dependencies before itself in order
80
+ @manifest_file.ordered_specs.each do |spec|
81
+ spec_git_repo = spec.source
82
+ spec_git_repo.use_details(spec.source_details) # This sets things like checked-out branch
74
83
 
75
84
  spec.paths.each do |from, to|
76
- from_path = "#{git_repo.local_dir}/#{from}"
85
+ # Note: spec_git_repo.local_dir is an absolute path.
86
+ from_path = "#{spec_git_repo.local_dir}/#{from}"
77
87
  if File.directory?(from_path)
78
88
  files = Dir["#{from_path}/**/*"].to_a + Dir["#{from_path}/**/.*"].to_a
79
89
  dirs = files.select { |file| File.directory?(file) }
80
90
  non_dirs = files - dirs
81
- subdirs << { from: from_path, to: to, dirs: dirs, non_dirs: non_dirs, source: git_repo }
91
+ operations << { cmd: "cp", from: from_path, to: to, dirs: dirs, non_dirs: non_dirs, comment: :single_dir }
82
92
  elsif from_path["*"] # If from_path contains at least one asterisk
83
93
  components = from.split("/")
84
94
  first_wild_idx = components.index { |item| item["*"] }
85
95
  no_wild_from_path = components[0..(first_wild_idx-1)].join("/")
86
96
  wild_path = components[first_wild_idx..-1].join("/")
87
97
 
88
- files = Dir["#{git_repo.local_dir}/#{no_wild_from_path}/#{wild_path}"].to_a
98
+ files = Dir["#{spec_git_repo.local_dir}/#{no_wild_from_path}/#{wild_path}"].to_a
89
99
  dirs = files.select { |file| File.directory?(file) }
90
100
  dirs += files.map { |f| File.dirname(f) }
91
101
  dirs.uniq!
92
102
 
93
103
  non_dirs = files - dirs
94
- subdirs << { from: "#{git_repo.local_dir}/#{no_wild_from_path}", to: to, dirs: dirs, non_dirs: non_dirs, source: git_repo }
104
+ operations << { cmd: "cp", from: "#{spec_git_repo.local_dir}/#{no_wild_from_path}", to: to, dirs: dirs, non_dirs: non_dirs, comment: :path_wildcard }
95
105
  else
96
106
  # A single file
97
- subdirs << { from: from_path, to: to, dirs: [], non_dirs: [from], source: git_repo }
107
+ operations << { cmd: "cp", from: from_path, to: to, dirs: [], non_dirs: [from_path], comment: :single_file }
98
108
  end
99
109
  end
100
110
  end
101
111
 
102
- subdirs
112
+ app_path = "#{File.expand_path(location)}/#{@manifest_file.app_root}"
113
+ app_files = Dir["#{app_path}/**/*"].to_a
114
+ app_dirs = app_files.select { |f| File.directory?(f) }
115
+ app_non_dirs = app_files - app_dirs
116
+ unless app_dirs.empty? && app_non_dirs.empty?
117
+ operations << { cmd: "cp", from: app_path, to: ".", dirs: app_dirs, non_dirs: app_non_dirs, comment: :app_files } # No source
118
+ end
119
+
120
+ operations
103
121
  end
104
122
 
105
- def assemble_app(location)
106
- dgd_root = "#{File.expand_path(location)}/#{GENERATED_ROOT}"
107
- app_path = "#{File.expand_path(location)}/#{@manifest_file.app_root}"
108
- FileUtils.rm_rf(dgd_root)
109
- FileUtils.cp_r(app_path, dgd_root)
123
+ public
124
+
125
+ def dgd_root(location)
126
+ "#{File.expand_path(location)}/#{GENERATED_ROOT}"
127
+ end
110
128
 
111
- write_config_file("#{location}/dgd.config")
112
- FileUtils.mkdir_p("#{location}/state") # Statedir for statedumps, editor files, etc.
129
+ def assemble_app(location, verbose:)
130
+ Dir[File.join(dgd_root(location), "*")].each { |dir| FileUtils.rm_rf dir }
113
131
 
114
- files_to_assemble.sort_by { |sd| sd[:to] }.each do |sd_hash|
115
- to_path = "#{dgd_root}/#{sd_hash[:to]}"
132
+ write_app_files(location, verbose: verbose)
133
+ end
116
134
 
117
- # Make appropriate dirs, including empty ones
118
- sd_hash[:dirs].each do |dir|
119
- FileUtils.mkdir_p dir.sub(sd_hash[:from], to_path)
120
- end
135
+ def update_app(location, verbose:)
136
+ write_app_files(location, verbose: verbose)
137
+ end
138
+
139
+ protected
140
+
141
+ def write_app_files(location, verbose:)
142
+ Dir.chdir(location) do
143
+ write_config_file("#{location}/dgd.config")
144
+ FileUtils.mkdir_p("#{location}/state") # Statedir for statedumps, editor files, etc.
145
+
146
+ assembly_operations(location, verbose: verbose).each do |sd_hash|
147
+ to_path = "#{dgd_root(location)}/#{sd_hash[:to]}"
148
+
149
+ if verbose
150
+ puts " Copy #{sd_hash[:from]} -> #{sd_hash[:to]}, files #{sd_hash[:non_dirs].join(", ")}"
151
+ end
121
152
 
122
- # Copy all files
123
- sd_hash[:non_dirs].each do |from_file|
124
- to_file = from_file.sub(sd_hash[:from], "#{dgd_root}/#{sd_hash[:to]}")
125
- to_dir = File.dirname(to_file)
126
- FileUtils.mkdir_p to_dir
127
- FileUtils.cp from_file, to_file
153
+ # Make appropriate dirs, including empty ones
154
+ sd_hash[:dirs].each do |dir|
155
+ FileUtils.mkdir_p dir.sub(sd_hash[:from], to_path)
156
+ end
157
+
158
+ # Copy all files
159
+ sd_hash[:non_dirs].each do |from_file|
160
+ to_file = from_file.sub(sd_hash[:from], "#{dgd_root(location)}/#{sd_hash[:to]}")
161
+ to_dir = File.dirname(to_file)
162
+ begin
163
+ FileUtils.mkdir_p to_dir
164
+ FileUtils.cp from_file, to_file
165
+ rescue
166
+ puts "Error when copying: #{from_file} -> #{to_file} in #{sd_hash.inspect}"
167
+ raise
168
+ end
169
+ end
128
170
  end
129
171
  end
130
172
  end
131
173
 
132
- def precheck(location)
133
- app_path = "#{File.expand_path(location)}/#{@manifest_file.app_root}"
134
- app_files = Dir["#{app_path}/**"].to_a.select { |f| !File.directory?(f) }
174
+ public
135
175
 
136
- subdirs = files_to_assemble
137
- all_files = subdirs.flat_map { |sd| sd[:non_dirs] } + app_files
138
- all_files.sort!
176
+ def precheck(location, verbose:)
177
+ all_files = assembly_operations(location, verbose: verbose).flat_map { |sd| sd[:non_dirs] }
139
178
 
140
179
  if all_files.size != all_files.uniq.size
141
180
  repeated = all_files.uniq.select { |f| all_files.count(f) > 1 }
@@ -145,40 +184,7 @@ module DGD::Manifest
145
184
 
146
185
  def write_config_file(path)
147
186
  File.open(path, "wb") do |f|
148
- f.write <<CONTENTS
149
- /* These are SkotOS limits. They are enormous. They should
150
- be configurable but they are not yet. */
151
- telnet_port = ([
152
- "*":50100 /* telnet port number */
153
- ]);
154
- binary_port = ([
155
- "*":50110 /* Failsafe */
156
- ]); /* binary ports */
157
- directory = "./#{GENERATED_ROOT}";
158
-
159
- users = 100; /* max # of users */
160
- editors = 40; /* max # of editor sessions */
161
- ed_tmpfile = "../state/ed"; /* proto editor tmpfile */
162
- swap_file = "../state/swap"; /* swap file */
163
- swap_size = 1048576; /* # sectors in swap file */
164
- sector_size = 512; /* swap sector size */
165
- swap_fragment = 4096; /* fragment to swap out */
166
- static_chunk = 64512; /* static memory chunk */
167
- dynamic_chunk = 261120; /* dynamic memory chunk */
168
- dump_file = "../state/dump"; /* dump file */
169
- dump_interval = 3600; /* dump interval */
170
-
171
- typechecking = 2; /* highest level of typechecking */
172
- include_file = "/include/std.h"; /* standard include file */
173
- include_dirs = ({ "/include", "~/include" }); /* directories to search */
174
- auto_object = "/kernel/lib/auto"; /* auto inherited object */
175
- driver_object = "/kernel/sys/driver"; /* driver object */
176
- create = "_F_create"; /* name of create function */
177
-
178
- array_size = 16384; /* max array size */
179
- objects = 262144; /* max # of objects */
180
- call_outs = 16384; /* max # of call_outs */
181
- CONTENTS
187
+ f.write @manifest_file.dgd_config.as_file
182
188
  end
183
189
  end
184
190
  end
@@ -192,8 +198,8 @@ CONTENTS
192
198
  def initialize(repo, git_url)
193
199
  @git_url = git_url
194
200
  @repo = repo
195
- local_path = git_url.tr("/\\", "_")
196
- @local_dir = "#{@repo.manifest_dir}/git/#{local_path}"
201
+ local_path = git_url.tr("/\\ ", "_")
202
+ @local_dir = "#{@repo.shared_dir}/git/#{local_path}"
197
203
 
198
204
  if File.directory?(@local_dir)
199
205
  Dir.chdir(@local_dir) do
@@ -205,40 +211,39 @@ CONTENTS
205
211
  end
206
212
 
207
213
  def default_branch
208
- return @default_branch if @default_branch
209
- output = `git rev-parse --abbrev-ref origin/HEAD`.chomp
210
- @default_branch = output.gsub(/^origin\//, "")
214
+ @default_branch ||= `git rev-parse --abbrev-ref origin/HEAD`.chomp.gsub(/^origin\//, "")
211
215
  end
212
216
 
213
217
  def use_details(details)
214
218
  if details["branch"]
215
219
  Dir.chdir(@local_dir) do
216
- DGD::Manifest.system_call("git checkout #{details["branch"]}")
220
+ DGD::Manifest.system_call("git checkout #{details["branch"]} && git pull")
217
221
  end
218
222
  else
219
223
  Dir.chdir(@local_dir) do
220
- DGD::Manifest.system_call("git checkout #{default_branch}")
224
+ DGD::Manifest.system_call("git checkout #{default_branch} && git pull")
221
225
  end
222
226
  end
223
227
  end
224
228
  end
225
229
 
230
+ # This class parses the DGD manifest
226
231
  class AppFile
227
232
  attr_reader :path
228
233
  attr_reader :repo
229
234
  attr_reader :specs
230
- attr_reader :app_root
235
+ attr_reader :dgd_config
236
+ attr_reader :shared_dir
231
237
 
232
- def initialize(repo, path)
238
+ def initialize(repo, path, shared_dir:)
233
239
  @path = path
234
240
  @repo = repo
241
+ @shared_dir = shared_dir
235
242
  raise("No such dgd.manifest file as #{path.inspect}!") unless File.exist?(path)
236
- contents = JSON.load(File.read(path))
243
+ contents = AppFile.parse_manifest_file(path)
237
244
 
238
245
  read_manifest_file(contents)
239
246
 
240
- @app_root = contents["app_root"] || "app"
241
-
242
247
  output_paths = @specs.flat_map { |s| s.paths.values }
243
248
  unless output_paths == output_paths.uniq
244
249
  repeated_paths = output_paths.select { |p| output_paths.count(p) > 1 }
@@ -254,20 +259,43 @@ CONTENTS
254
259
  #else
255
260
  # puts "This dgd.manifest needs the default Kernel Library."
256
261
  # # This app has specified no kernellib paths -- add them
257
- # git_repo = @repo.git_repo(DEFAULT_KERNELLIB_URL)
262
+ # spec_git_repo = @repo.git_repo(DEFAULT_KERNELLIB_URL)
258
263
  # klib_spec = GoodsSpec.new @repo, name: "default Kernel Library",
259
- # source: git_repo, paths: KERNEL_PATH_MAP
264
+ # source: spec_git_repo, paths: KERNEL_PATH_MAP
260
265
  # specs.unshift klib_spec
261
266
  #end
262
267
 
263
268
  nil
264
269
  end
265
270
 
271
+ # Load the JSON and then remove comments
272
+ def self.parse_manifest_file(path)
273
+ contents = JSON.parse(File.read path)
274
+ remove_comments!(contents)
275
+ contents
276
+ end
277
+
278
+ def self.remove_comments!(items)
279
+ if items.is_a?(Hash)
280
+ items.delete_if { |k, v| k[0] == "#" }
281
+ items.values.each { |v| remove_comments!(v) }
282
+ elsif items.is_a?(Array)
283
+ items.delete_if { |i| i.is_a?(String) && i[0] == "#" }
284
+ items.each { |i| remove_comments!(i) }
285
+ end
286
+ end
287
+
266
288
  def read_manifest_file(contents)
267
289
  raise "Expected a top-level JSON object in dgd.manifest!" unless contents.is_a?(Hash)
268
290
 
269
291
  @specs = []
270
292
 
293
+ @dgd_config = DGDRuntimeConfig.new (contents["config"] || {})
294
+
295
+ if contents["app_root"]
296
+ raise "App_root must now be inside config block!"
297
+ end
298
+
271
299
  if contents["unbundled_goods"]
272
300
  raise "Unbundled_goods must be an array!" unless contents["unbundled_goods"].is_a?(Array)
273
301
 
@@ -279,7 +307,10 @@ CONTENTS
279
307
 
280
308
  @specs += contents["goods"].map do |goods_url|
281
309
  begin
282
- json_contents = JSON.parse(URI.open(goods_url).read)
310
+ text_contents = URI.open(goods_url).read
311
+ local_path = shared_dir + "/goods/" + goods_url.tr("/\\ ", "_")
312
+ File.open(local_path, "wb") { |f| f.write(text_contents) }
313
+ json_contents = JSON.parse text_contents
283
314
  rescue
284
315
  STDERR.puts "Error reading or parsing by URL: #{goods_url.inspect}"
285
316
  raise
@@ -289,9 +320,15 @@ CONTENTS
289
320
  end
290
321
  end
291
322
 
323
+ def app_root
324
+ @dgd_config.app_root
325
+ end
326
+
292
327
  def unbundled_json_to_spec(fields)
293
328
  source = nil
294
329
  source_details = nil
330
+ dependencies = []
331
+
295
332
  if fields["git"]
296
333
  raise "A git source requires a git url: #{fields.inspect}!" unless fields["git"]["url"]
297
334
  source = @repo.git_repo(fields["git"]["url"])
@@ -304,9 +341,46 @@ CONTENTS
304
341
  raise "Paths in Goods files must map strings to strings! #{fields["paths"].inspect}"
305
342
  end
306
343
 
307
- spec = GoodsSpec.new(@repo, name: fields["name"], source: source, source_details: source_details, paths: fields["paths"])
344
+ if fields["dependencies"]
345
+ # For now, permit a single string as a dependency.
346
+ fields["dependencies"] = [ fields["dependencies"] ] if fields["dependencies"].is_a?(String)
347
+
348
+ goods_url = nil
349
+ fields["dependencies"].each do |dep|
350
+ if dep.is_a?(String)
351
+ goods_url = dep
352
+ elsif dep.is_a?(Hash)
353
+ raise "Currently only URL-based dependencies on Goods files are supported!" unless dep["url"]
354
+ goods_url = dep["url"]
355
+ else
356
+ raise "Unexpected dependency type #{dep.class} when parsing DGD Manifest specs, item: #{dep.inspect}"
357
+ end
358
+
359
+ text_contents = URI.open(goods_url).read
360
+ local_path = shared_dir + "/goods/" + goods_url.tr("/\\ ", "_")
361
+ File.open(local_path, "wb") { |f| f.write(text_contents) }
362
+ dep_fields = JSON.parse text_contents
363
+
364
+ dependencies.push unbundled_json_to_spec(dep_fields)
365
+ end
366
+ end
367
+
368
+ spec = GoodsSpec.new(@repo, name: fields["name"], source: source, source_details: source_details, paths: fields["paths"], dependencies: dependencies)
308
369
  return spec
309
370
  end
371
+
372
+ def ordered_specs
373
+ @specs.flat_map do |s|
374
+ deps = [s]
375
+ deps_to_add = s.dependencies
376
+ while(deps_to_add.size > 0)
377
+ next_deps = deps_to_add.flat_map { |dep| dep.dependencies }
378
+ deps = deps_to_add + deps
379
+ deps_to_add = next_deps
380
+ end
381
+ deps
382
+ end
383
+ end
310
384
  end
311
385
 
312
386
  class GoodsSpec
@@ -315,8 +389,9 @@ CONTENTS
315
389
  attr_reader :source
316
390
  attr_reader :source_details
317
391
  attr_reader :paths
392
+ attr_reader :dependencies
318
393
 
319
- def initialize(repo, name:, source:, source_details: {}, paths:)
394
+ def initialize(repo, name:, source:, source_details: {}, paths:, dependencies:)
320
395
  @repo = repo
321
396
  @name = name
322
397
  @source = source
@@ -329,6 +404,211 @@ CONTENTS
329
404
  end
330
405
 
331
406
  @paths = cleaned_paths
407
+ @dependencies = dependencies
408
+ end
409
+ end
410
+
411
+ class AppDirectory
412
+ attr_reader :location
413
+ attr_accessor :name
414
+
415
+ DEFAULT_FILE_LOCATIONS = {
416
+ "manifest" => "dgd.manifest",
417
+ "gitignore" => ".gitignore",
418
+ "gems_rb" => "gems.rb",
419
+ }
420
+ DEFAULT_EMPTY_DIRS = [ "app", "state" ]
421
+
422
+ def initialize(directory)
423
+ @location = directory
424
+ end
425
+
426
+ def gitignore_contents
427
+ <<~FILE_CONTENTS
428
+ # DGD Manifest files
429
+ .root
430
+ dgd.config
431
+ state/*
432
+ FILE_CONTENTS
433
+ end
434
+
435
+ def manifest_contents
436
+ <<FILE_CONTENTS
437
+ {
438
+ "name": "#{@name}",
439
+ "version": "0.1.0",
440
+ "description": "TODO: put description here",
441
+ "app_root": "app",
442
+ "goods": [
443
+ "# This is an example goods file - substitute your own.",
444
+ "https://raw.githubusercontent.com/ChatTheatre/dgd-tools/main/goods/skotos_httpd.goods"
445
+ ],
446
+ "unbundled_goods": [
447
+ {
448
+ "#": "this is an example of unbundled goods - substitute your own",
449
+ "name": "kernellib",
450
+ "git": {
451
+ "url": "https://github.com/ChatTheatre/kernellib.git",
452
+ "branch": "master"
453
+ },
454
+ "paths": {
455
+ "src/doc/kernel": "doc/kernel",
456
+ "src/include/kernel": "include/kernel",
457
+ "src/include/*.h": "include",
458
+ "src/kernel": "kernel"
459
+ }
460
+ }
461
+ ]
462
+ }
463
+ FILE_CONTENTS
464
+ end
465
+
466
+ def gems_rb_contents
467
+ <<~FILE_CONTENTS
468
+ source "https://rubygems.org"
469
+
470
+ gem "dgd-tools", ">= #{DGD::VERSION}"
471
+ FILE_CONTENTS
472
+ end
473
+
474
+ def create!
475
+ if File.exist?(@location) && (!File.directory?(@location) || Dir["#{@location}/**"].size != 0)
476
+ raise "Cannot create a new DGD manifest project over a file or in an existing non-empty directory!"
477
+ end
478
+
479
+ puts "Creating new DGD manifest project at #{@location}..."
480
+ FileUtils.mkdir_p @location
481
+ Dir.chdir @location do
482
+ DEFAULT_FILE_LOCATIONS.each do |file_desc, file_location|
483
+ File.open(file_location, "wb") do |f|
484
+ contents = send("#{file_desc}_contents")
485
+ f.write(contents)
486
+ end
487
+ end
488
+
489
+ DEFAULT_EMPTY_DIRS.each do |dir|
490
+ FileUtils.mkdir dir
491
+ FileUtils.touch File.join(dir, ".keep")
492
+ end
493
+
494
+ result = system "bundle"
495
+ raise("Could not run bundler to install dgd-tools for #{@location}!") unless result
496
+
497
+ result = system "bundle exec dgd-manifest install"
498
+ raise("Error when running dgd-manifest for #{@location}!") unless result
499
+ end
500
+
501
+ puts "Successfully created project at #{@location}."
502
+ end
503
+ end
504
+
505
+ class DGDRuntimeConfig
506
+ attr_reader :app_root
507
+
508
+ DEFAULT_CONFIG = {
509
+ users: 100,
510
+ editors: 40,
511
+ swap_size: 1048576,
512
+ sector_size: 512,
513
+ swap_fragment: 4096,
514
+ static_chunk: 64512,
515
+ dynamic_chunk: 261120,
516
+ dump_interval: 3600,
517
+ typechecking: 2,
518
+ include_file: "/include/std.h",
519
+ include_dirs: ["/include", "~/include"],
520
+ auto_object: "/kernel/lib/auto",
521
+ driver_object: "/kernel/sys/driver",
522
+ create: "_F_create",
523
+ array_size: 16384,
524
+ objects: 262144,
525
+ call_outs: 16384,
526
+ }
527
+ CONFIG_KEYS = DEFAULT_CONFIG.keys.map(&:to_s) + [ "app_root", "ports", "telnet_ports", "dump_file", "statedir" ]
528
+
529
+ def initialize(config_data)
530
+ @app_root = config_data["app_root"] || "app"
531
+ @ports = {
532
+ "*" => 50100,
533
+ }
534
+ @telnet_ports = {
535
+ "*" => 50110,
536
+ }
537
+ @statedir = config_data["statedir"] || "state"
538
+ @dump_file = if config_data["dump_file"]
539
+ "../" + config_data["dump_file"]
540
+ else
541
+ "../#{@statedir}/dump"
542
+ end
543
+ @config = DEFAULT_CONFIG.dup
544
+
545
+ @raw_data = config_data
546
+ @config.keys.each do |prop|
547
+ # For now, assume and require that JSON data is the correct type if present
548
+ @config[prop] = config_data[prop.to_s] if config_data[prop.to_s]
549
+ end
550
+ unexpected_config_keys = config_data.keys - CONFIG_KEYS
551
+ unless unexpected_config_keys.empty?
552
+ raise "Unexpected key names in DGD configuration: #{unexpected_config_keys.inspect}!"
553
+ end
554
+
555
+ if config_data["telnet_ports"]
556
+ @telnet_ports = config_to_ports(config_data["telnet_ports"])
557
+ end
558
+ if config_data["ports"]
559
+ @ports = config_to_ports(config_data["ports"])
560
+ end
561
+ end
562
+
563
+ def config_to_ports(data)
564
+ if data.is_a?(Hash)
565
+ # TODO: verify that keys are IP addr strings and values are legal port numbers
566
+ return data
567
+ elsif data.is_a?(Array)
568
+ # TODO: verify that data is an array of legal integer port numbers
569
+ ports = {}
570
+ data.each { |p| ports["*"] = p }
571
+ return ports
572
+ elsif data.is_a?(Integer)
573
+ return { "*": data }
574
+ else
575
+ raise "dgd-manifest: not sure how to get port data from a #{data.class.name} -- #{data.inspect}!"
576
+ end
577
+ end
578
+
579
+ def as_file
580
+ return <<DGD_CONFIG
581
+ telnet_port = ([
582
+ #{@telnet_ports.map { |ip, p| "#{ip.inspect}:#{p}" }.join(",\n ") }
583
+ ]); /* legacy telnet ports */
584
+ binary_port = ([
585
+ #{@ports.map { |ip, p| "#{ip.inspect}:#{p}" }.join(",\n ") }
586
+ ]); /* binary ports */
587
+ directory = "./#{GENERATED_ROOT}";
588
+
589
+ users = #{@config[:users]}; /* max # of connections */
590
+ editors = #{@config[:editors]}; /* max # of built-in-editor sessions */
591
+ ed_tmpfile = "../#{@statedir}/ed"; /* proto editor tmpfile */
592
+ swap_file = "../#{@statedir}/swap"; /* swap file */
593
+ swap_size = #{@config[:swap_size]}; /* # sectors in swap file */
594
+ sector_size = #{@config[:sector_size]}; /* swap sector size */
595
+ swap_fragment = #{@config[:swap_fragment]}; /* fragment to swap out */
596
+ static_chunk = #{@config[:static_chunk]}; /* static memory chunk */
597
+ dynamic_chunk = #{@config[:dynamic_chunk]}; /* dynamic memory chunk */
598
+ dump_file = #{@dump_file.inspect}; /* dump file */
599
+ dump_interval = #{@config[:dump_interval]}; /* expected statedump interval in seconds */
600
+
601
+ typechecking = #{@config[:typechecking]}; /* level of typechecking (2 is highest) */
602
+ include_file = #{@config[:include_file].inspect}; /* standard include file */
603
+ include_dirs = ({ #{@config[:include_dirs].map(&:inspect).join(", ")} }); /* directories to search */
604
+ auto_object = #{@config[:auto_object].inspect}; /* auto inherited object */
605
+ driver_object = #{@config[:driver_object].inspect}; /* driver object */
606
+ create = #{@config[:create].inspect}; /* name of create function */
607
+
608
+ array_size = #{@config[:array_size]}; /* max array size */
609
+ objects = #{@config[:objects]}; /* max # of objects */
610
+ call_outs = #{@config[:call_outs]}; /* max # of callouts */
611
+ DGD_CONFIG
332
612
  end
333
613
  end
334
614
  end
@@ -8,11 +8,21 @@ require "tempfile"
8
8
 
9
9
  module SkotOS; end
10
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
+
11
19
  class SkotOS::XMLObject
12
20
  attr_reader :pretty
21
+ attr_reader :noko_doc
13
22
 
14
- def initialize(pretty)
23
+ def initialize(pretty, noko_doc: nil)
15
24
  @pretty = pretty
25
+ @noko_doc = noko_doc
16
26
  end
17
27
 
18
28
  def self.from_file(filename)
@@ -23,9 +33,7 @@ class SkotOS::XMLObject
23
33
  remove_undiffed(doc)
24
34
 
25
35
  pretty = doc.to_xml(indent:3)
26
- #data = doc.to_hash
27
- #prune_whitespace(data)
28
- SkotOS::XMLObject.new pretty
36
+ SkotOS::XMLObject.new pretty, noko_doc: doc
29
37
  end
30
38
 
31
39
  def self.diff_between(obj1, obj2, o1_name: "Object 1", o2_name: "Object 2")
@@ -38,8 +46,13 @@ class SkotOS::XMLObject
38
46
  of1.close
39
47
  of2.close
40
48
 
49
+ diff_opts = [ "c" ]
50
+ diff_opts += [ "b", "B" ] if self.ignore_whitespace
51
+
41
52
  # Diff 'fails' if there's a difference between the two files.
42
- diff = system_call("diff -c #{of1.path} #{of2.path}", fail_ok: true)
53
+ cmd = "diff -#{diff_opts.join("")} #{of1.path} #{of2.path}"
54
+ #puts "Diff command: #{cmd}"
55
+ diff = system_call(cmd, fail_ok: true)
43
56
  diff.sub!(of1.path, o1_name)
44
57
  diff.sub!(of2.path, o2_name)
45
58
  ensure
@@ -49,21 +62,35 @@ class SkotOS::XMLObject
49
62
  diff
50
63
  end
51
64
 
65
+ def self.skip_ignored_files(list, base_dir)
66
+ if self.merry_only
67
+ list.select { |path| File.directory?(base_dir + "/" + path) ||
68
+ path[/.xml$/] || path[/.XML$/] }
69
+ else
70
+ list.select do |path|
71
+ !path[/,v$/] && # Ignore files ending in comma-v
72
+ !path[/-backup-\d+-\d+-\d+\.xml/] && # Ignore files ending in -backup-[DATE].xml
73
+ path != ".git" && # Ignore .git directories
74
+ path != "MOVED" # Ignore MOVED - it's a sort of recycle, waiting to be emptied
75
+ end
76
+ end
77
+ end
78
+
52
79
  def self.diff_dirs(dir1, dir2)
53
- entries1 = Dir.glob("*", base: dir1).to_a
54
- entries2 = Dir.glob("*", base: dir2).to_a
80
+ entries1 = skip_ignored_files(Dir.glob("*", base: dir1).to_a, dir1)
81
+ entries2 = skip_ignored_files(Dir.glob("*", base: dir2).to_a, dir2)
55
82
 
56
83
  only_in_1 = entries1 - entries2
57
84
  only_in_2 = entries2 - entries1
58
85
  in_both = entries1 & entries2
59
86
 
60
87
  diff = []
61
- diff << "Only in first: #{only_in_1.join(", ")}" unless only_in_1.empty?
62
- diff << "Only in second: #{only_in_2.join(", ")}" unless only_in_2.empty?
88
+ diff << "Only in first: #{only_in_1.map { |s| dir1 + "/" + s }.join(", ")}" unless only_in_1.empty?
89
+ diff << "Only in second: #{only_in_2.map { |s| dir2 + "/" + s }.join(", ")}" unless only_in_2.empty?
63
90
 
64
91
  in_both.each do |file|
65
- in_1 = "#{dir1}/#{file}"
66
- in_2 = "#{dir2}/#{file}"
92
+ in_1 = File.join dir1, file
93
+ in_2 = File.join dir2, file
67
94
  if File.directory?(in_1) ^ File.directory?(in_2)
68
95
  diff << "Only a directory in one, not both: #{dir1}/#{file}"
69
96
  elsif File.directory?(in_1)
@@ -72,22 +99,122 @@ class SkotOS::XMLObject
72
99
  else
73
100
  o1 = from_file(in_1)
74
101
  o2 = from_file(in_2)
75
- diff << diff_between(o1, o2, o1_name: in_1, o2_name: in_2)
102
+ this_diff = diff_between(o1, o2, o1_name: in_1, o2_name: in_2)
103
+ diff << this_diff unless this_diff.strip == ""
76
104
  end
77
105
  end
78
106
  diff
79
107
  end
80
108
 
81
109
  def self.remove_undiffed(doc)
82
- if doc.root && doc.root.element? && doc.root.attribute("owner")
83
- doc.root.remove_attribute("owner")
110
+ if doc.root && doc.root.element?
111
+ ignored_top_elements = ["program", "clone", "owner"]
112
+ ignored_top_elements.each do |attr|
113
+ if doc.root.attribute(attr)
114
+ doc.root.remove_attribute(attr)
115
+ end
116
+ end
117
+ end
118
+
119
+ rev = noko_single_node(doc.root, "Core:Property", attrs: { "property" => "revisions" })
120
+ noko_remove(rev) if rev
121
+
122
+ list = noko_single_node(doc.root, "Core:Property", attrs: { "property" => "#list#" })
123
+ list.remove if list
124
+
125
+ properties = noko_with_name_and_attrs(doc.root, "Core:Property")
126
+ properties.each do |prop_node|
127
+ prop_node.remove if prop_node.attribute("property").value.start_with?("sys:sync")
128
+ end
129
+
130
+ if self.merry_only
131
+ # Kill off all the non-Merry nodes
132
+ noko_remove_non_merry_nodes(doc.root)
133
+ end
134
+
135
+ if self.ignore_types
136
+ self.ignore_types.each do |ignored_type|
137
+ skipped = noko_with_name_and_attrs(doc.root, ignored_type)
138
+ skipped.each { |n| noko_remove(n) }
139
+ end
140
+ end
141
+
142
+ base_combat = noko_single_node(doc.root, "Base:Combat")
143
+ if base_combat
144
+ base_strength = noko_single_node(base_combat, "Base:Strength", attrs: { "value" => "1" })
145
+ base_max_fatigue = noko_single_node(base_combat, "Base:MaxFatigue", attrs: { "value" => "1" })
146
+ if base_strength && base_max_fatigue && noko_non_text(base_combat.children).size == 2
147
+ next_text = base_combat.next
148
+ base_combat.remove
149
+ next_text.remove
150
+ end
151
+ end
152
+ end
153
+
154
+ def self.noko_remove(node)
155
+ nn = node.next
156
+ nn.remove if nn.is_a?(Nokogiri::XML::Text)
157
+ node.remove
158
+ end
159
+
160
+ def self.noko_single_node(node, name, attrs: {})
161
+ choices = noko_with_name_and_attrs(node, name, attrs)
162
+ if choices.size < 1
163
+ nil
164
+ elsif choices.size > 1
165
+ raise "Single-node search returned more than one node! #{name.inspect}, #{attrs.inspect}"
166
+ else
167
+ choices[0]
168
+ end
169
+ end
170
+
171
+ def self.noko_non_text(nodes)
172
+ nodes.select { |n| !n.is_a? Nokogiri::XML::Text }
173
+ end
174
+
175
+ def self.noko_with_name_and_attrs(node, name, attrs = {})
176
+ results = node.children.flat_map { |n| noko_with_name_and_attrs(n, name, attrs) }
177
+ if node.name == name &&
178
+ attrs.all? { |k, v| node.attribute(k).value == v }
179
+ results << node
180
+ end
181
+ results
182
+ end
183
+
184
+ def self.noko_remove_non_merry_nodes(root)
185
+ root.children.each do |node|
186
+ if node.name != "Core:PropertyContainer"
187
+ node.remove
188
+ next
189
+ end
190
+
191
+ node.children.each do |node2|
192
+ if node2.name != "Core:PCProperties"
193
+ node2.remove
194
+ next
195
+ end
196
+
197
+ node2.children.each do |property_node|
198
+ if property_node.name != "Core:Property" || property_node.attribute("property").value[0..5] != "merry:"
199
+ property_node.remove
200
+ next
201
+ end
202
+ # Leave the Merry node alone
203
+ end
204
+
205
+ if node2.children.size == 0
206
+ node2.remove
207
+ end
208
+ end
209
+ if node.children.size == 0
210
+ node.remove
211
+ end
84
212
  end
85
213
  end
86
214
 
87
215
  def self.system_call(cmd, fail_ok: false)
88
216
  f = Tempfile.new("system_call_xml_diff_")
89
217
  begin
90
- puts "Running command: #{cmd.inspect}..."
91
218
  system(cmd, out: f)
92
219
  unless fail_ok || $?.success?
93
220
  f.rewind
@@ -1,3 +1,3 @@
1
1
  module DGD
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.8"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dgd-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.8
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-11-11 00:00:00.000000000 Z
11
+ date: 2021-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
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
27
41
  description: dgd-tools supplies DGD Manifest and eventually perhaps other tools. DGD
28
42
  Manifest is an experimental DGD library and packaging system.
29
43
  email:
@@ -49,8 +63,11 @@ files:
49
63
  - example_xml/PropertyTypes.xml
50
64
  - example_xml/Thing.xml
51
65
  - example_xml/Thing2.xml
66
+ - example_xml/t1/Thing.xml
67
+ - example_xml/t2/Thing.xml
52
68
  - exe/dgd-manifest
53
69
  - exe/skotos-xml-diff
70
+ - goods/chattheatre_kernellib.goods
54
71
  - goods/skotos_httpd.goods
55
72
  - lib/dgd-tools/manifest.rb
56
73
  - lib/dgd-tools/skotos_xml_obj.rb