dgd-tools 0.1.6 → 0.1.11

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: f64aa1b29bc2fb6a0c10292a25246ce4fbe5a43b7fa23dc19a84ab01abc58013
4
- data.tar.gz: 60fb058d6f25ca80348e5236450ed3c5fe812137d04e0fce237d36b6b252dfca
3
+ metadata.gz: 838abb7957cb56eb3f096e8e345b3afda6d12db91a6110b7fa07029d589124d5
4
+ data.tar.gz: aa0a2551b3763eb418f99352cb0d40d58bcae92ff50bfce31bff22c644ae7019
5
5
  SHA512:
6
- metadata.gz: 30b54b93e1b4e5da44c7cf0bc6e526dd489d74301df20cd4d9644c69aab509416d3082d2646dd6394331f81643a92cbc24933141fdca024057ed6e003866b37a
7
- data.tar.gz: 525305f71a39842d01f7ce99f3bce1d85d7dc0e5493411e09759cf003a8632609130281f40ba8ff25bf7edb8ffa73de5e9b53d3c3022c4143a8b99be70a9c4f9
6
+ metadata.gz: a661db27d13a97f9086c7802769e1a08ac3f21f7c120f8bea6a6efe6e332a3d2ecb7e2571e0020dd36c8188019af907023662e248c1c1ffadfd9ee3c1b9c5b2a
7
+ data.tar.gz: 15e3742398cb16222e176dc5bc9385445219e23fce1a0cdf9205ced86ad47570c4c422d637734386f6f79e154282c3f57cf02784f9e4238f1ec8208dc4944878
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dgd-tools (0.1.5)
4
+ dgd-tools (0.1.10)
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
data/exe/dgd-manifest CHANGED
@@ -1,59 +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"
7
- end
6
+ SUB_COMMANDS = %w(new test install update server)
8
7
 
9
- if ARGV == ["--version"]
10
- puts "dgd-tools version #{DGD::VERSION}"
11
- exit
12
- end
8
+ OPTS = Optimist::options do
9
+ version "DGD-tools version #{DGD::VERSION}"
10
+ banner <<BANNER
11
+ Use dgd.manifest to assemble your DGD application.
13
12
 
14
- if ARGV.size == 1 && ["-h", "--help"].include?(ARGV[0])
15
- puts <<HELP_INFO
16
- dgd-manifest commands:
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
17
19
 
18
- new [project_name]: create a new DGD-manifest project
19
- test: make sure the dgd.manifest file is well-formed and usable
20
- install: compile the DGD application to a config file and a root directory
21
- HELP_INFO
22
- exit
20
+ Available options:
21
+ BANNER
22
+ opt :verbose, "Print verbose output where available"
23
+ stop_on SUB_COMMANDS
23
24
  end
24
25
 
25
- case ARGV[0]
26
- when "new"
27
- unless ARGV.size == 2
28
- puts "Usage: dgd-manifest new [project name]"
29
- raise "Must supply exactly one argument to dgd-manifest new!"
30
- end
31
- appdir = DGD::Manifest::AppDirectory.new(File.expand_path ARGV[1])
32
- appdir.name = ARGV[1]
33
- appdir.create!
34
- when "test"
35
- unless File.exist?("dgd.manifest")
36
- raise "I don't see a dgd.manifest file in this directory!"
37
- end
38
- puts "Running dgd.manifest installer..."
39
- repo = DGD::Manifest::Repo.new
40
- repo.manifest_file("dgd.manifest")
41
- repo.precheck(".")
42
- puts "Verified Manifest packages: this looks likely correct."
43
- when "install"
44
- unless File.exist?("dgd.manifest")
45
- 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: OPTS[: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, verbose: OPTS[:verbose])
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}"
46
82
  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)
53
- puts "Assembled DGD application into #{current_dir}"
54
- when "server"
55
- puts "Starting DGD server..."
56
- DGD::Manifest.system_call("~/.dgd-tools/dgd/bin/dgd dgd.config")
57
- else
58
- raise "Unrecognised #{$0} command: #{ARGV[0].inspect}!"
59
- end
data/exe/skotos-xml-diff CHANGED
@@ -4,28 +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 == 1 && ["-h", "--help"].include?(ARGV[0])
10
- print <<HELP_INFO
11
- skotos-xml-diff [file1] [file2]
12
-
13
- OR
14
-
15
- skotos-xml-diff [dir1] [dir2]
16
- HELP_INFO
17
- exit
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
18
21
  end
19
22
 
20
- if ARGV.size != 2
21
- STDERR.puts "Usage: skotos-xml-diff <obj_or_dir_1> <obj_or_dir_2>"
22
- exit -1
23
- 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])
24
25
 
25
- if File.directory?(ARGV[0]) ^ File.directory?(ARGV[1])
26
- STDERR.puts "Please give two files or two directories, not one of each!"
27
- exit -1
28
- end
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]
29
29
 
30
30
  if File.directory?(ARGV[0])
31
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,14 +16,13 @@ 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
 
@@ -65,25 +64,26 @@ module DGD::Manifest
65
64
  raise "Already have a dgd.manifest file!" unless @no_manifest_file
66
65
 
67
66
  @no_manifest_file = false
68
- @manifest_file ||= AppFile.new(self, path)
67
+ @manifest_file ||= AppFile.new(self, path, shared_dir: shared_dir)
69
68
  end
70
69
 
71
70
  protected
72
71
 
73
72
  # This includes files to assemble... But also subdirectories and commands. This format is
74
73
  # unstable and ugly, and should not be exposed to outside parties who might later depend on it.
75
- def assembly_operations(location)
74
+ def assembly_operations(location, verbose:)
76
75
  operations = []
77
76
 
78
77
  raise("No manifest file!") if @no_manifest_file
79
78
 
80
- @manifest_file.specs.each do |spec|
81
- git_repo = spec.source
82
- git_repo.use_details(spec.source_details) # This sets things like checked-out branch
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
83
83
 
84
84
  spec.paths.each do |from, to|
85
- # Note: git_repo.local_dir is an absolute path.
86
- 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}"
87
87
  if File.directory?(from_path)
88
88
  files = Dir["#{from_path}/**/*"].to_a + Dir["#{from_path}/**/.*"].to_a
89
89
  dirs = files.select { |file| File.directory?(file) }
@@ -95,13 +95,13 @@ module DGD::Manifest
95
95
  no_wild_from_path = components[0..(first_wild_idx-1)].join("/")
96
96
  wild_path = components[first_wild_idx..-1].join("/")
97
97
 
98
- 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
99
99
  dirs = files.select { |file| File.directory?(file) }
100
100
  dirs += files.map { |f| File.dirname(f) }
101
101
  dirs.uniq!
102
102
 
103
103
  non_dirs = files - dirs
104
- operations << { cmd: "cp", from: "#{git_repo.local_dir}/#{no_wild_from_path}", to: to, dirs: dirs, non_dirs: non_dirs, comment: :path_wildcard }
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 }
105
105
  else
106
106
  # A single file
107
107
  operations << { cmd: "cp", from: from_path, to: to, dirs: [], non_dirs: [from_path], comment: :single_file }
@@ -122,16 +122,33 @@ module DGD::Manifest
122
122
 
123
123
  public
124
124
 
125
- def assemble_app(location)
126
- dgd_root = "#{File.expand_path(location)}/#{GENERATED_ROOT}"
127
- FileUtils.rm_rf(dgd_root)
125
+ def dgd_root(location)
126
+ "#{File.expand_path(location)}/#{GENERATED_ROOT}"
127
+ end
128
+
129
+ def assemble_app(location, verbose:)
130
+ Dir[File.join(dgd_root(location), "*")].each { |dir| FileUtils.rm_rf dir }
131
+
132
+ write_app_files(location, verbose: verbose)
133
+ end
134
+
135
+ def update_app(location, verbose:)
136
+ write_app_files(location, verbose: verbose)
137
+ end
138
+
139
+ protected
128
140
 
141
+ def write_app_files(location, verbose:)
129
142
  Dir.chdir(location) do
130
143
  write_config_file("#{location}/dgd.config")
131
144
  FileUtils.mkdir_p("#{location}/state") # Statedir for statedumps, editor files, etc.
132
145
 
133
- assembly_operations(location).each do |sd_hash|
134
- to_path = "#{dgd_root}/#{sd_hash[:to]}"
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
135
152
 
136
153
  # Make appropriate dirs, including empty ones
137
154
  sd_hash[:dirs].each do |dir|
@@ -140,17 +157,24 @@ module DGD::Manifest
140
157
 
141
158
  # Copy all files
142
159
  sd_hash[:non_dirs].each do |from_file|
143
- to_file = from_file.sub(sd_hash[:from], "#{dgd_root}/#{sd_hash[:to]}")
160
+ to_file = from_file.sub(sd_hash[:from], "#{dgd_root(location)}/#{sd_hash[:to]}")
144
161
  to_dir = File.dirname(to_file)
145
- FileUtils.mkdir_p to_dir
146
- FileUtils.cp from_file, 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
147
169
  end
148
170
  end
149
171
  end
150
172
  end
151
173
 
152
- def precheck(location)
153
- all_files = assembly_operations(location).flat_map { |sd| sd[:non_dirs] }
174
+ public
175
+
176
+ def precheck(location, verbose:)
177
+ all_files = assembly_operations(location, verbose: verbose).flat_map { |sd| sd[:non_dirs] }
154
178
 
155
179
  if all_files.size != all_files.uniq.size
156
180
  repeated = all_files.uniq.select { |f| all_files.count(f) > 1 }
@@ -160,40 +184,7 @@ module DGD::Manifest
160
184
 
161
185
  def write_config_file(path)
162
186
  File.open(path, "wb") do |f|
163
- f.write <<CONTENTS
164
- /* These are SkotOS limits. They are larger than you are likely to need. They should
165
- be configurable but they are not yet. */
166
- telnet_port = ([
167
- "*":50100 /* telnet port number */
168
- ]);
169
- binary_port = ([
170
- "*":50110 /* Failsafe */
171
- ]); /* binary ports */
172
- directory = "./#{GENERATED_ROOT}";
173
-
174
- users = 100; /* max # of users */
175
- editors = 40; /* max # of editor sessions */
176
- ed_tmpfile = "../state/ed"; /* proto editor tmpfile */
177
- swap_file = "../state/swap"; /* swap file */
178
- swap_size = 1048576; /* # sectors in swap file */
179
- sector_size = 512; /* swap sector size */
180
- swap_fragment = 4096; /* fragment to swap out */
181
- static_chunk = 64512; /* static memory chunk */
182
- dynamic_chunk = 261120; /* dynamic memory chunk */
183
- dump_file = "../state/dump"; /* dump file */
184
- dump_interval = 3600; /* dump interval */
185
-
186
- typechecking = 2; /* highest level of typechecking */
187
- include_file = "/include/std.h"; /* standard include file */
188
- include_dirs = ({ "/include", "~/include" }); /* directories to search */
189
- auto_object = "/kernel/lib/auto"; /* auto inherited object */
190
- driver_object = "/kernel/sys/driver"; /* driver object */
191
- create = "_F_create"; /* name of create function */
192
-
193
- array_size = 16384; /* max array size */
194
- objects = 262144; /* max # of objects */
195
- call_outs = 16384; /* max # of call_outs */
196
- CONTENTS
187
+ f.write @manifest_file.dgd_config.as_file
197
188
  end
198
189
  end
199
190
  end
@@ -207,7 +198,7 @@ CONTENTS
207
198
  def initialize(repo, git_url)
208
199
  @git_url = git_url
209
200
  @repo = repo
210
- local_path = git_url.tr("/\\", "_")
201
+ local_path = git_url.tr("/\\ ", "_")
211
202
  @local_dir = "#{@repo.shared_dir}/git/#{local_path}"
212
203
 
213
204
  if File.directory?(@local_dir)
@@ -226,32 +217,33 @@ CONTENTS
226
217
  def use_details(details)
227
218
  if details["branch"]
228
219
  Dir.chdir(@local_dir) do
229
- DGD::Manifest.system_call("git checkout #{details["branch"]}")
220
+ DGD::Manifest.system_call("git checkout #{details["branch"]} && git pull")
230
221
  end
231
222
  else
232
223
  Dir.chdir(@local_dir) do
233
- DGD::Manifest.system_call("git checkout #{default_branch}")
224
+ DGD::Manifest.system_call("git checkout #{default_branch} && git pull")
234
225
  end
235
226
  end
236
227
  end
237
228
  end
238
229
 
230
+ # This class parses the DGD manifest
239
231
  class AppFile
240
232
  attr_reader :path
241
233
  attr_reader :repo
242
234
  attr_reader :specs
243
- attr_reader :app_root
235
+ attr_reader :dgd_config
236
+ attr_reader :shared_dir
244
237
 
245
- def initialize(repo, path)
238
+ def initialize(repo, path, shared_dir:)
246
239
  @path = path
247
240
  @repo = repo
241
+ @shared_dir = shared_dir
248
242
  raise("No such dgd.manifest file as #{path.inspect}!") unless File.exist?(path)
249
243
  contents = AppFile.parse_manifest_file(path)
250
244
 
251
245
  read_manifest_file(contents)
252
246
 
253
- @app_root = contents["app_root"] || "app"
254
-
255
247
  output_paths = @specs.flat_map { |s| s.paths.values }
256
248
  unless output_paths == output_paths.uniq
257
249
  repeated_paths = output_paths.select { |p| output_paths.count(p) > 1 }
@@ -267,9 +259,9 @@ CONTENTS
267
259
  #else
268
260
  # puts "This dgd.manifest needs the default Kernel Library."
269
261
  # # This app has specified no kernellib paths -- add them
270
- # git_repo = @repo.git_repo(DEFAULT_KERNELLIB_URL)
262
+ # spec_git_repo = @repo.git_repo(DEFAULT_KERNELLIB_URL)
271
263
  # klib_spec = GoodsSpec.new @repo, name: "default Kernel Library",
272
- # source: git_repo, paths: KERNEL_PATH_MAP
264
+ # source: spec_git_repo, paths: KERNEL_PATH_MAP
273
265
  # specs.unshift klib_spec
274
266
  #end
275
267
 
@@ -298,6 +290,12 @@ CONTENTS
298
290
 
299
291
  @specs = []
300
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
+
301
299
  if contents["unbundled_goods"]
302
300
  raise "Unbundled_goods must be an array!" unless contents["unbundled_goods"].is_a?(Array)
303
301
 
@@ -309,7 +307,10 @@ CONTENTS
309
307
 
310
308
  @specs += contents["goods"].map do |goods_url|
311
309
  begin
312
- 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
313
314
  rescue
314
315
  STDERR.puts "Error reading or parsing by URL: #{goods_url.inspect}"
315
316
  raise
@@ -319,9 +320,15 @@ CONTENTS
319
320
  end
320
321
  end
321
322
 
323
+ def app_root
324
+ @dgd_config.app_root
325
+ end
326
+
322
327
  def unbundled_json_to_spec(fields)
323
328
  source = nil
324
329
  source_details = nil
330
+ dependencies = []
331
+
325
332
  if fields["git"]
326
333
  raise "A git source requires a git url: #{fields.inspect}!" unless fields["git"]["url"]
327
334
  source = @repo.git_repo(fields["git"]["url"])
@@ -334,9 +341,46 @@ CONTENTS
334
341
  raise "Paths in Goods files must map strings to strings! #{fields["paths"].inspect}"
335
342
  end
336
343
 
337
- 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)
338
369
  return spec
339
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
340
384
  end
341
385
 
342
386
  class GoodsSpec
@@ -345,8 +389,9 @@ CONTENTS
345
389
  attr_reader :source
346
390
  attr_reader :source_details
347
391
  attr_reader :paths
392
+ attr_reader :dependencies
348
393
 
349
- def initialize(repo, name:, source:, source_details: {}, paths:)
394
+ def initialize(repo, name:, source:, source_details: {}, paths:, dependencies:)
350
395
  @repo = repo
351
396
  @name = name
352
397
  @source = source
@@ -359,6 +404,7 @@ CONTENTS
359
404
  end
360
405
 
361
406
  @paths = cleaned_paths
407
+ @dependencies = dependencies
362
408
  end
363
409
  end
364
410
 
@@ -395,7 +441,7 @@ CONTENTS
395
441
  "app_root": "app",
396
442
  "goods": [
397
443
  "# This is an example goods file - substitute your own.",
398
- "https://raw.githubusercontent.com/noahgibbs/dgd-tools/main/goods/skotos_httpd.goods"
444
+ "https://raw.githubusercontent.com/ChatTheatre/dgd-tools/main/goods/skotos_httpd.goods"
399
445
  ],
400
446
  "unbundled_goods": [
401
447
  {
@@ -455,4 +501,117 @@ FILE_CONTENTS
455
501
  puts "Successfully created project at #{@location}."
456
502
  end
457
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
+ return data.map { |ip, port| [ip, Integer(port) ] }
566
+ elsif data.is_a?(Array)
567
+ if data[0].is_a?(Array)
568
+ ports = data.map { |ip, port| [ip, Integer(port) ] }
569
+ return ports
570
+ end
571
+
572
+ ports = data.map { |p| [ "*", Integer(p) ] }
573
+ STDERR.puts "Arrayified: #{ports.inspect}"
574
+ return ports
575
+ elsif data.is_a?(Integer)
576
+ return [ [ "*", data ] ]
577
+ else
578
+ raise "dgd-manifest: not sure how to get port data from a #{data.class.name} -- #{data.inspect}!"
579
+ end
580
+ end
581
+
582
+ def as_file
583
+ return <<DGD_CONFIG
584
+ telnet_port = ([
585
+ #{@telnet_ports.map { |ip, p| "#{ip.inspect}:#{p}" }.join(",\n ") }
586
+ ]); /* legacy telnet ports */
587
+ binary_port = ([
588
+ #{@ports.map { |ip, p| "#{ip.inspect}:#{p}" }.join(",\n ") }
589
+ ]); /* binary ports */
590
+ directory = "./#{GENERATED_ROOT}";
591
+
592
+ users = #{@config[:users]}; /* max # of connections */
593
+ editors = #{@config[:editors]}; /* max # of built-in-editor sessions */
594
+ ed_tmpfile = "../#{@statedir}/ed"; /* proto editor tmpfile */
595
+ swap_file = "../#{@statedir}/swap"; /* swap file */
596
+ swap_size = #{@config[:swap_size]}; /* # sectors in swap file */
597
+ sector_size = #{@config[:sector_size]}; /* swap sector size */
598
+ swap_fragment = #{@config[:swap_fragment]}; /* fragment to swap out */
599
+ static_chunk = #{@config[:static_chunk]}; /* static memory chunk */
600
+ dynamic_chunk = #{@config[:dynamic_chunk]}; /* dynamic memory chunk */
601
+ dump_file = #{@dump_file.inspect}; /* dump file */
602
+ dump_interval = #{@config[:dump_interval]}; /* expected statedump interval in seconds */
603
+
604
+ typechecking = #{@config[:typechecking]}; /* level of typechecking (2 is highest) */
605
+ include_file = #{@config[:include_file].inspect}; /* standard include file */
606
+ include_dirs = ({ #{@config[:include_dirs].map(&:inspect).join(", ")} }); /* directories to search */
607
+ auto_object = #{@config[:auto_object].inspect}; /* auto inherited object */
608
+ driver_object = #{@config[:driver_object].inspect}; /* driver object */
609
+ create = #{@config[:create].inspect}; /* name of create function */
610
+
611
+ array_size = #{@config[:array_size]}; /* max array size */
612
+ objects = #{@config[:objects]}; /* max # of objects */
613
+ call_outs = #{@config[:call_outs]}; /* max # of callouts */
614
+ DGD_CONFIG
615
+ end
616
+ end
458
617
  end
@@ -8,13 +8,21 @@ require "tempfile"
8
8
 
9
9
  module SkotOS; end
10
10
 
11
- # TODO: remove <Core:Property property="revisions"> from anywhere in the XML tree
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
12
18
 
13
19
  class SkotOS::XMLObject
14
20
  attr_reader :pretty
21
+ attr_reader :noko_doc
15
22
 
16
- def initialize(pretty)
23
+ def initialize(pretty, noko_doc: nil)
17
24
  @pretty = pretty
25
+ @noko_doc = noko_doc
18
26
  end
19
27
 
20
28
  def self.from_file(filename)
@@ -25,9 +33,7 @@ class SkotOS::XMLObject
25
33
  remove_undiffed(doc)
26
34
 
27
35
  pretty = doc.to_xml(indent:3)
28
- #data = doc.to_hash
29
- #prune_whitespace(data)
30
- SkotOS::XMLObject.new pretty
36
+ SkotOS::XMLObject.new pretty, noko_doc: doc
31
37
  end
32
38
 
33
39
  def self.diff_between(obj1, obj2, o1_name: "Object 1", o2_name: "Object 2")
@@ -40,8 +46,13 @@ class SkotOS::XMLObject
40
46
  of1.close
41
47
  of2.close
42
48
 
49
+ diff_opts = [ "c" ]
50
+ diff_opts += [ "b", "B" ] if self.ignore_whitespace
51
+
43
52
  # Diff 'fails' if there's a difference between the two files.
44
- 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)
45
56
  diff.sub!(of1.path, o1_name)
46
57
  diff.sub!(of2.path, o2_name)
47
58
  ensure
@@ -51,28 +62,35 @@ class SkotOS::XMLObject
51
62
  diff
52
63
  end
53
64
 
54
- def self.skip_ignored_files(list)
55
- list.select do |path|
56
- !path[/,v$/] && # Ignore files ending in comma-v
57
- !path[/-backup-\d+-\d+-\d+\.xml/] # Ignore files ending in -backup-[DATE].xml
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
58
76
  end
59
77
  end
60
78
 
61
79
  def self.diff_dirs(dir1, dir2)
62
- entries1 = skip_ignored_files(Dir.glob("*", base: dir1).to_a)
63
- entries2 = skip_ignored_files(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)
64
82
 
65
83
  only_in_1 = entries1 - entries2
66
84
  only_in_2 = entries2 - entries1
67
85
  in_both = entries1 & entries2
68
86
 
69
87
  diff = []
70
- diff << "Only in first: #{only_in_1.join(", ")}" unless only_in_1.empty?
71
- 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?
72
90
 
73
91
  in_both.each do |file|
74
- in_1 = "#{dir1}/#{file}"
75
- in_2 = "#{dir2}/#{file}"
92
+ in_1 = File.join dir1, file
93
+ in_2 = File.join dir2, file
76
94
  if File.directory?(in_1) ^ File.directory?(in_2)
77
95
  diff << "Only a directory in one, not both: #{dir1}/#{file}"
78
96
  elsif File.directory?(in_1)
@@ -97,6 +115,101 @@ class SkotOS::XMLObject
97
115
  end
98
116
  end
99
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
212
+ end
100
213
  end
101
214
 
102
215
  def self.system_call(cmd, fail_ok: false)
@@ -1,3 +1,3 @@
1
1
  module DGD
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.11"
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.6
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Gibbs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-02-04 00:00:00.000000000 Z
11
+ date: 2021-03-18 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:
@@ -53,6 +67,7 @@ files:
53
67
  - example_xml/t2/Thing.xml
54
68
  - exe/dgd-manifest
55
69
  - exe/skotos-xml-diff
70
+ - goods/chattheatre_kernellib.goods
56
71
  - goods/skotos_httpd.goods
57
72
  - lib/dgd-tools/manifest.rb
58
73
  - lib/dgd-tools/skotos_xml_obj.rb