dgd-tools 0.1.6 → 0.1.11

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: 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