dgd-tools 0.1.5 → 0.1.10

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