dgd-tools 0.1.5 → 0.1.10

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: 7248b5e9b0f257568633b8f83c166430e1451da62c0d84acfa468f48a6bf4196
4
- data.tar.gz: 435dfa244ae63087b45acd18f9220267cf4facf560f2c1a235392d6ec9fe00f6
3
+ metadata.gz: df6b6f5495201cda1059723bc87848c81ef6d112449fd147aed1a6c08873b13f
4
+ data.tar.gz: f5adb6e0b687e71002c25fdf694375b06b1c3e400a6a5adaa0f759ae502abea8
5
5
  SHA512:
6
- metadata.gz: 0f6534cced478794994f6a85ebf10277d6c0455d095708a49901b3527bea1534a9d18b2c5dc6bffc5b3a03bf5949fd352644b019148f541f1e6f98660aee060b
7
- data.tar.gz: 86c72de588f15ce2f1804068771e46a6ca71f707399fc807b74bdca02c52ef173dab11058eb24475e387852443de79c59cd553d9902a79611240a39f1ae72431
6
+ metadata.gz: 91624cda3d356f2dc5d17e7a003a1ef21bf989c94ef5761e6c265d0fffc08fac932a59dd4e1da9f824e6f9cf79d9746eb9a33bff44a87d7693f9b34476852ecd
7
+ data.tar.gz: 2a3efbd628524723b1f7e15982dea8077088fd305a68cfd186a0a0396ac3f321c117e6d1301398b9b80c922524f479a4b6b97c8736b3116d9d7f7793f8fa38b1
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.4)
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,48 +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)
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
8
19
 
9
- if ARGV == ["--version"]
10
- puts "dgd-tools version #{DGD::VERSION}"
11
- exit
20
+ Available options:
21
+ BANNER
22
+ opt :verbose, "Print verbose output where available"
23
+ stop_on SUB_COMMANDS
12
24
  end
13
25
 
14
- case ARGV[0]
15
- when "new"
16
- unless ARGV.size == 2
17
- puts "Usage: dgd-manifest new [project name]"
18
- raise "Must supply exactly one argument to dgd-manifest new!"
19
- end
20
- appdir = DGD::Manifest::AppDirectory.new(File.expand_path ARGV[1])
21
- appdir.name = ARGV[1]
22
- appdir.create!
23
- when "test"
24
- unless File.exist?("dgd.manifest")
25
- raise "I don't see a dgd.manifest file in this directory!"
26
- end
27
- puts "Running dgd.manifest installer..."
28
- repo = DGD::Manifest::Repo.new
29
- repo.manifest_file("dgd.manifest")
30
- repo.precheck(".")
31
- puts "Verified Manifest packages: this looks likely correct."
32
- when "install"
33
- unless File.exist?("dgd.manifest")
34
- 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}"
35
82
  end
36
- puts "Running DGD Manifest installer..."
37
- repo = DGD::Manifest::Repo.new
38
- repo.manifest_file("dgd.manifest")
39
- current_dir = File.expand_path(".")
40
- repo.precheck(current_dir)
41
- repo.assemble_app(current_dir)
42
- puts "Assembled DGD application into #{current_dir}"
43
- when "server"
44
- puts "Starting DGD server..."
45
- DGD::Manifest.system_call("~/.dgd-tools/dgd/bin/dgd dgd.config")
46
- else
47
- raise "Unrecognised #{$0} command: #{ARGV[0].inspect}!"
48
- 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
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,9 +259,9 @@ 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
 
@@ -285,6 +290,12 @@ CONTENTS
285
290
 
286
291
  @specs = []
287
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
+
288
299
  if contents["unbundled_goods"]
289
300
  raise "Unbundled_goods must be an array!" unless contents["unbundled_goods"].is_a?(Array)
290
301
 
@@ -296,7 +307,10 @@ CONTENTS
296
307
 
297
308
  @specs += contents["goods"].map do |goods_url|
298
309
  begin
299
- 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
300
314
  rescue
301
315
  STDERR.puts "Error reading or parsing by URL: #{goods_url.inspect}"
302
316
  raise
@@ -306,9 +320,15 @@ CONTENTS
306
320
  end
307
321
  end
308
322
 
323
+ def app_root
324
+ @dgd_config.app_root
325
+ end
326
+
309
327
  def unbundled_json_to_spec(fields)
310
328
  source = nil
311
329
  source_details = nil
330
+ dependencies = []
331
+
312
332
  if fields["git"]
313
333
  raise "A git source requires a git url: #{fields.inspect}!" unless fields["git"]["url"]
314
334
  source = @repo.git_repo(fields["git"]["url"])
@@ -321,9 +341,46 @@ CONTENTS
321
341
  raise "Paths in Goods files must map strings to strings! #{fields["paths"].inspect}"
322
342
  end
323
343
 
324
- 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)
325
369
  return spec
326
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
327
384
  end
328
385
 
329
386
  class GoodsSpec
@@ -332,8 +389,9 @@ CONTENTS
332
389
  attr_reader :source
333
390
  attr_reader :source_details
334
391
  attr_reader :paths
392
+ attr_reader :dependencies
335
393
 
336
- def initialize(repo, name:, source:, source_details: {}, paths:)
394
+ def initialize(repo, name:, source:, source_details: {}, paths:, dependencies:)
337
395
  @repo = repo
338
396
  @name = name
339
397
  @source = source
@@ -346,6 +404,7 @@ CONTENTS
346
404
  end
347
405
 
348
406
  @paths = cleaned_paths
407
+ @dependencies = dependencies
349
408
  end
350
409
  end
351
410
 
@@ -382,7 +441,7 @@ CONTENTS
382
441
  "app_root": "app",
383
442
  "goods": [
384
443
  "# This is an example goods file - substitute your own.",
385
- "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"
386
445
  ],
387
446
  "unbundled_goods": [
388
447
  {
@@ -442,4 +501,114 @@ FILE_CONTENTS
442
501
  puts "Successfully created project at #{@location}."
443
502
  end
444
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
612
+ end
613
+ end
445
614
  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.5"
2
+ VERSION = "0.1.10"
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.5
4
+ version: 0.1.10
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-01-21 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