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