simp-metadata 0.4.4 → 0.5.2
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 +5 -5
- data/README.md +494 -0
- data/Rakefile +19 -22
- data/exe/simp-install +1 -1
- data/exe/simp-media +1 -1
- data/exe/simp-metadata +1 -1
- data/lib/simp/install/command.rb +34 -35
- data/lib/simp/media.rb +0 -1
- data/lib/simp/media/command.rb +34 -35
- data/lib/simp/media/engine.rb +29 -35
- data/lib/simp/media/type.rb +1 -2
- data/lib/simp/media/type/base.rb +12 -4
- data/lib/simp/media/type/control-repo.rb +96 -107
- data/lib/simp/media/type/internet.rb +8 -8
- data/lib/simp/media/type/iso.rb +0 -1
- data/lib/simp/media/type/local.rb +18 -19
- data/lib/simp/media/type/tar.rb +37 -37
- data/lib/simp/metadata.rb +240 -258
- data/lib/simp/metadata/bootstrap_source.rb +93 -89
- data/lib/simp/metadata/buildinfo.rb +23 -23
- data/lib/simp/metadata/command.rb +60 -58
- data/lib/simp/metadata/commands.rb +1 -1
- data/lib/simp/metadata/commands/base.rb +28 -25
- data/lib/simp/metadata/commands/clone.rb +3 -5
- data/lib/simp/metadata/commands/component.rb +128 -90
- data/lib/simp/metadata/commands/delete.rb +4 -5
- data/lib/simp/metadata/commands/pry.rb +1 -3
- data/lib/simp/metadata/commands/release.rb +22 -23
- data/lib/simp/metadata/commands/releases.rb +1 -3
- data/lib/simp/metadata/commands/save.rb +10 -13
- data/lib/simp/metadata/commands/script.rb +11 -14
- data/lib/simp/metadata/commands/search.rb +15 -20
- data/lib/simp/metadata/commands/set-write-url.rb +1 -3
- data/lib/simp/metadata/commands/set-write.rb +1 -3
- data/lib/simp/metadata/commands/update.rb +9 -10
- data/lib/simp/metadata/component.rb +310 -154
- data/lib/simp/metadata/components.rb +15 -16
- data/lib/simp/metadata/engine.rb +31 -39
- data/lib/simp/metadata/fake_uri.rb +2 -0
- data/lib/simp/metadata/location.rb +99 -105
- data/lib/simp/metadata/locations.rb +19 -21
- data/lib/simp/metadata/release.rb +30 -39
- data/lib/simp/metadata/releases.rb +14 -15
- data/lib/simp/metadata/source.rb +69 -79
- data/lib/simp/metadata/version.rb +9 -0
- data/spec/simp/media/command_spec.rb +4 -5
- data/spec/simp/media/engine_spec.rb +14 -14
- data/spec/simp/media/type/control_repo_spec.rb +10 -12
- data/spec/simp/media/type/internet_spec.rb +11 -11
- data/spec/simp/media/type/iso_spec.rb +6 -7
- data/spec/simp/media/type/local_spec.rb +6 -8
- data/spec/simp/media/type/tar_spec.rb +6 -8
- data/spec/simp/metadata/buildinfo_spec.rb +19 -17
- data/spec/simp/metadata/commands/clone_spec.rb +4 -3
- data/spec/simp/metadata/component_spec.rb +43 -54
- data/spec/simp/metadata/engine_spec.rb +38 -41
- data/spec/simp/metadata/release_spec.rb +72 -79
- data/spec/simp/metadata/source_spec.rb +8 -6
- data/spec/simp/metadata_spec.rb +95 -98
- data/spec/spec_helper.rb +33 -21
- metadata +5 -3
@@ -6,169 +6,157 @@ module Simp
|
|
6
6
|
def initialize(options, engine)
|
7
7
|
super(options, engine)
|
8
8
|
@cleanup = []
|
9
|
-
if
|
10
|
-
raise
|
9
|
+
if options['output'].nil?
|
10
|
+
raise 'output must be specified for control-repo output'
|
11
11
|
end
|
12
|
-
@origtempdir = Dir.mktmpdir(
|
12
|
+
@origtempdir = Dir.mktmpdir('cachedir')
|
13
13
|
@repopath = "#{@origtempdir}/control-repo"
|
14
14
|
FileUtils.mkdir_p(@repopath)
|
15
15
|
@cleanup << @origtempdir
|
16
|
-
exit_code = run("git clone #{options[
|
17
|
-
unless
|
18
|
-
uri = URI(options[
|
19
|
-
if
|
16
|
+
exit_code = run("git clone #{options['output']} #{@repopath}")
|
17
|
+
unless exit_code.success?
|
18
|
+
uri = URI(options['output'])
|
19
|
+
if uri.scheme == 'file'
|
20
20
|
FileUtils.mkdir_p(uri.path)
|
21
21
|
Dir.chdir(uri.path) do |path|
|
22
|
-
if
|
23
|
-
run(
|
22
|
+
if path =~ /.*\.git$/
|
23
|
+
run('git init --bare')
|
24
24
|
else
|
25
|
-
run(
|
25
|
+
run('git init')
|
26
26
|
end
|
27
27
|
|
28
|
-
run("git clone #{options[
|
28
|
+
run("git clone #{options['output']} #{@repopath}")
|
29
29
|
end
|
30
30
|
else
|
31
|
-
raise
|
31
|
+
raise 'output is not a valid control-repo'
|
32
32
|
end
|
33
33
|
end
|
34
|
-
Dir.chdir(@repopath) do
|
35
|
-
exit_code = run("git checkout #{options[
|
34
|
+
Dir.chdir(@repopath) do
|
35
|
+
exit_code = run("git checkout #{options['branch']}")
|
36
36
|
unless exit_code.success?
|
37
|
-
exit_code = run("git checkout -b #{options[
|
38
|
-
unless
|
39
|
-
raise "error, unable to checkout #{options[
|
37
|
+
exit_code = run("git checkout -b #{options['branch']}")
|
38
|
+
unless exit_code.success?
|
39
|
+
raise "error, unable to checkout #{options['branch']} in git repo #{uri}"
|
40
40
|
end
|
41
41
|
end
|
42
|
-
@branch = options[
|
43
|
-
|
44
|
-
@branch = options[
|
45
|
-
exit_code = run("git checkout -b #{options[
|
42
|
+
@branch = options['branch']
|
43
|
+
unless options['destination_branch'].nil?
|
44
|
+
@branch = options['destination_branch']
|
45
|
+
exit_code = run("git checkout -b #{options['destination_branch']}")
|
46
46
|
unless exit_code.success?
|
47
|
-
|
47
|
+
raise "error, unable to create branch #{options['destination_branch']} in git repo #{uri}"
|
48
48
|
end
|
49
49
|
end
|
50
|
-
run(
|
51
|
-
run(
|
50
|
+
run('rm -rf SIMP/modules')
|
51
|
+
run('rm -rf SIMP/assets')
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
def add_component(component, fetch_return_value)
|
56
|
-
if
|
56
|
+
if options['embed']
|
57
57
|
# XXX ToDo: Copy components to control-repo if embed == true
|
58
58
|
case component.component_type
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
exit_code = run("cd #{fetch_return_value['path']} && git --work-tree=\"#{outputpath}\" checkout #{component.version} .")
|
66
|
-
else
|
67
|
-
exit_code = run("cd #{fetch_return_value['path']} && tar -cf - . | tar -xvpf - -C \"#{outputpath}\"")
|
68
|
-
end
|
69
|
-
|
70
|
-
unless exit_code.success?
|
71
|
-
error "unable to copy #{component.name} to #{outputpath}: error code #{exit_code.exitstatus}"
|
72
|
-
end
|
73
|
-
when "puppet-module"
|
74
|
-
subdirectory = "SIMP/modules"
|
75
|
-
outputpath = "#{@repopath}/#{subdirectory}/#{component.module_name}"
|
76
|
-
debug2("Copying #{component.module_name} to #{outputpath}")
|
77
|
-
FileUtils.mkdir_p(outputpath)
|
59
|
+
when 'documentation'
|
60
|
+
when 'simp-metadata'
|
61
|
+
subdirectory = 'SIMP/metadata'
|
62
|
+
outputpath = "#{@repopath}/#{subdirectory}/#{component.name}"
|
63
|
+
FileUtils.mkdir_p(outputpath)
|
64
|
+
if Dir.exist?("#{fetch_return_value['path']}/.git")
|
78
65
|
exit_code = run("cd #{fetch_return_value['path']} && git --work-tree=\"#{outputpath}\" checkout #{component.version} .")
|
79
|
-
unless exit_code.success?
|
80
|
-
error "unable to copy #{component.module_name} to #{outputpath}: error code #{exit_code.exitstatus}"
|
81
|
-
end
|
82
66
|
else
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
67
|
+
exit_code = run("cd #{fetch_return_value['path']} && tar -cf - . | tar -xvpf - -C \"#{outputpath}\"")
|
68
|
+
end
|
69
|
+
|
70
|
+
unless exit_code.success?
|
71
|
+
error "unable to copy #{component.name} to #{outputpath}: error code #{exit_code.exitstatus}"
|
72
|
+
end
|
73
|
+
when 'puppet-module'
|
74
|
+
subdirectory = 'SIMP/modules'
|
75
|
+
outputpath = "#{@repopath}/#{subdirectory}/#{component.module_name}"
|
76
|
+
debug2("Copying #{component.module_name} to #{outputpath}")
|
77
|
+
FileUtils.mkdir_p(outputpath)
|
78
|
+
exit_code = run("cd #{fetch_return_value['path']} && git --work-tree=\"#{outputpath}\" checkout #{component.version} .")
|
79
|
+
unless exit_code.success?
|
80
|
+
error "unable to copy #{component.module_name} to #{outputpath}: error code #{exit_code.exitstatus}"
|
81
|
+
end
|
82
|
+
else
|
83
|
+
subdirectory = "SIMP/assets/#{component.name}"
|
84
|
+
case component.output_type
|
85
|
+
when :file
|
86
|
+
FileUtils.mkdir_p("#{@repopath}/#{subdirectory}")
|
87
|
+
FileUtils.cp(fetch_return_value['path'], "#{@repopath}/#{subdirectory}/#{component.output_filename}")
|
88
|
+
end
|
89
89
|
end
|
90
90
|
else
|
91
91
|
# XXX ToDo: Add necessary references to generate the puppetfile during finalize
|
92
|
-
raise
|
92
|
+
raise 'not yet implemented'
|
93
93
|
end
|
94
|
-
|
95
94
|
end
|
96
95
|
|
97
|
-
def finalize(
|
96
|
+
def finalize(_manifest)
|
98
97
|
# XXX ToDo: Generate Puppetfile (if options["embed"] == false)
|
99
98
|
# Otherwise copy to control-repo
|
100
99
|
# XXX ToDo: Munge Puppetfile
|
101
100
|
environmentconf = "#{@repopath}/environment.conf"
|
102
101
|
hierayaml = "#{@repopath}/hiera.yaml"
|
103
102
|
# XXX ToDo: Munge hiera.yaml
|
104
|
-
|
105
|
-
if
|
106
|
-
self.munge_environmentconf(environmentconf)
|
107
|
-
end
|
103
|
+
munge_hierayaml(hierayaml)
|
104
|
+
munge_environmentconf(environmentconf) if options['embed']
|
108
105
|
run("cd #{@repopath} && git add -A")
|
109
|
-
run("cd #{@repopath} && git commit -m \"simp-install: upgrade to #{options[
|
106
|
+
run("cd #{@repopath} && git commit -m \"simp-install: upgrade to #{options['version']}\"")
|
110
107
|
run("cd #{@repopath} && git push origin #{@branch}")
|
111
108
|
end
|
112
109
|
|
113
110
|
def munge_environmentconf(environmentconf)
|
114
111
|
# Munge environment.conf to add SIMP/modules to modulepath
|
115
|
-
if
|
112
|
+
if File.exist?(environmentconf)
|
116
113
|
data = File.read(environmentconf).split("\n")
|
117
114
|
data.each_with_index do |line, fileline|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
if
|
131
|
-
newarray = []
|
132
|
-
paths.each do |path, index|
|
133
|
-
newarray << path
|
134
|
-
if (index == module_index)
|
135
|
-
newarray << "SIMP/modules"
|
136
|
-
end
|
137
|
-
end
|
138
|
-
data[fileline] = "modulepath = #{newarray.join(":")}"
|
139
|
-
File.open(environmentconf, "w") { |f| f.write(data.join("\n")) }
|
140
|
-
end
|
115
|
+
next unless /^modulepath = (?<capture>.*)$/ =~ line
|
116
|
+
paths = capture.split(':')
|
117
|
+
found = false
|
118
|
+
module_index = nil
|
119
|
+
paths.each_with_index do |path, index|
|
120
|
+
module_index = index if path =~ /modules/
|
121
|
+
found = true if path =~ /simp\/modules/
|
122
|
+
end
|
123
|
+
next if found
|
124
|
+
newarray = []
|
125
|
+
paths.each do |path, index|
|
126
|
+
newarray << path
|
127
|
+
newarray << 'SIMP/modules' if index == module_index
|
141
128
|
end
|
129
|
+
data[fileline] = "modulepath = #{newarray.join(':')}"
|
130
|
+
File.open(environmentconf, 'w') { |f| f.write(data.join("\n")) }
|
142
131
|
end
|
143
132
|
else
|
144
|
-
File.open(environmentconf,
|
133
|
+
File.open(environmentconf, 'w') { |f| f.write("modulepath = modules:SIMP/modules:$basemodulepath\n") }
|
145
134
|
end
|
146
135
|
end
|
147
136
|
|
148
137
|
def munge_hierayaml(hierayaml)
|
149
138
|
data = {}
|
150
|
-
if
|
139
|
+
if File.exist?(hierayaml)
|
151
140
|
data = YAML.load(File.read(hierayaml))
|
152
|
-
version = data[
|
141
|
+
version = data['version']
|
153
142
|
case version
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
end
|
163
|
-
end
|
164
|
-
if (found == false)
|
165
|
-
hash = {"name" => "SIMP Compliance Engine", "lookup_key" => "compliance_markup::enforcement"}
|
166
|
-
data["hierarchy"] << hash
|
143
|
+
when 4
|
144
|
+
# XXX ToDo: Add version 4 hiera.yaml support
|
145
|
+
raise "currently version 4 hiera.yaml's are not supported"
|
146
|
+
when 5
|
147
|
+
found = false
|
148
|
+
data['hierarchy'].each_with_index do |hash|
|
149
|
+
if hash['lookup_key'] == 'compliance_markup::enforcement'
|
150
|
+
found = true
|
167
151
|
end
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
152
|
+
end
|
153
|
+
unless found
|
154
|
+
hash = { 'name' => 'SIMP Compliance Engine', 'lookup_key' => 'compliance_markup::enforcement' }
|
155
|
+
data['hierarchy'] << hash
|
156
|
+
end
|
157
|
+
when nil
|
158
|
+
# XXX ToDo: Add version 3 hiera.yaml support
|
159
|
+
raise "currently version 3 hiera.yaml's are not supported"
|
172
160
|
end
|
173
161
|
else
|
174
162
|
raw_yaml = <<-EOF
|
@@ -198,9 +186,10 @@ hierarchy:
|
|
198
186
|
EOF
|
199
187
|
data = YAML.load(raw_yaml)
|
200
188
|
end
|
201
|
-
File.open(hierayaml,
|
189
|
+
File.open(hierayaml, 'w') { |f| f.write(data.to_yaml) }
|
202
190
|
end
|
203
|
-
|
191
|
+
|
192
|
+
def cleanup
|
204
193
|
@cleanup.each do |path|
|
205
194
|
FileUtils.rmtree(path)
|
206
195
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
require 'open3'
|
3
2
|
|
4
3
|
module Simp
|
@@ -6,28 +5,29 @@ module Simp
|
|
6
5
|
module Type
|
7
6
|
class Internet < Simp::Media::Type::Base
|
8
7
|
attr_accessor :options
|
8
|
+
|
9
9
|
def initialize(options, engine)
|
10
10
|
@cleanup = []
|
11
11
|
super(options, engine)
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
14
|
+
attr_writer :input_directory
|
15
|
+
|
17
16
|
def input_directory
|
18
|
-
if
|
19
|
-
target = Dir.mktmpdir(
|
17
|
+
if @input_directory.nil?
|
18
|
+
target = Dir.mktmpdir('cachedir')
|
20
19
|
@cleanup << target
|
21
20
|
@input_directory = target
|
22
21
|
else
|
23
22
|
@input_directory
|
24
23
|
end
|
25
24
|
end
|
25
|
+
|
26
26
|
def fetch_component(component, options)
|
27
|
-
Simp::Metadata.download_component(component, options.merge(
|
27
|
+
Simp::Metadata.download_component(component, options.merge('target' => input_directory))
|
28
28
|
end
|
29
29
|
|
30
|
-
def cleanup
|
30
|
+
def cleanup
|
31
31
|
@cleanup.each do |path|
|
32
32
|
FileUtils.rmtree(path)
|
33
33
|
end
|
data/lib/simp/media/type/iso.rb
CHANGED
@@ -3,32 +3,31 @@ module Simp
|
|
3
3
|
module Media
|
4
4
|
module Type
|
5
5
|
class Local < Simp::Media::Type::Base
|
6
|
-
|
7
|
-
|
8
|
-
end
|
6
|
+
attr_writer :input_directory
|
7
|
+
|
9
8
|
def input_directory
|
10
|
-
@options[
|
9
|
+
@options['input']
|
11
10
|
end
|
11
|
+
|
12
12
|
def fetch_component(component, options)
|
13
13
|
retval = {}
|
14
14
|
|
15
15
|
case component.class.to_s
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
retval["path"] = "#{options["input"]}/#{subdirectory}/#{component.name}"
|
16
|
+
when 'String'
|
17
|
+
retval['path'] = "#{options['input']}/simp/metadata/#{component.name}"
|
18
|
+
when 'Simp::Metadata::Component'
|
19
|
+
# XXX ToDo: Add manifest.yaml support so we don't need this logic at all
|
20
|
+
subdirectory = case component.component_type
|
21
|
+
when 'documentation'
|
22
|
+
'simp/docs'
|
23
|
+
when 'puppet-module'
|
24
|
+
'simp/modules'
|
25
|
+
else
|
26
|
+
'simp/assets'
|
27
|
+
end
|
28
|
+
retval['path'] = "#{options['input']}/#{subdirectory}/#{component.name}"
|
30
29
|
end
|
31
|
-
|
30
|
+
retval
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
data/lib/simp/media/type/tar.rb
CHANGED
@@ -3,64 +3,64 @@ module Simp
|
|
3
3
|
module Media
|
4
4
|
module Type
|
5
5
|
class Tar < Simp::Media::Type::Base
|
6
|
-
|
7
6
|
def initialize(options, engine)
|
8
7
|
@cleanup = []
|
9
8
|
|
10
|
-
@origtempdir = Dir.mktmpdir(
|
11
|
-
@tempdir = @origtempdir +
|
9
|
+
@origtempdir = Dir.mktmpdir('cachedir')
|
10
|
+
@tempdir = @origtempdir + '/' + File.basename(options['output'], '.*')
|
12
11
|
@cleanup << @origtempdir
|
13
12
|
super(options, engine)
|
14
13
|
end
|
15
14
|
|
16
15
|
def add_component(component, fetch_return_value)
|
17
|
-
case component.component_type
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
16
|
+
subdirectory = case component.component_type
|
17
|
+
when 'documentation'
|
18
|
+
'SIMP/docs'
|
19
|
+
when 'simp-metadata'
|
20
|
+
'SIMP/metadata'
|
21
|
+
when 'puppet-module'
|
22
|
+
'SIMP/modules'
|
23
|
+
else
|
24
|
+
"SIMP/assets/#{component.name}"
|
25
|
+
end
|
27
26
|
case component.output_type
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
FileUtils.cp_r(fetch_return_value["path"] + "/.", @tempdir + "/#{subdirectory}/#{component.output_filename}")
|
34
|
-
else
|
35
|
-
raise "Unable to find component #{component.name} in input source: path=#{fetch_return_value["path"]}"
|
36
|
-
end
|
37
|
-
when :file
|
38
|
-
if (File.exists?(fetch_return_value["path"]))
|
39
|
-
FileUtils.mkdir_p(@tempdir + "/#{subdirectory}")
|
40
|
-
FileUtils.cp_r(fetch_return_value["path"], @tempdir + "/#{subdirectory}/#{component.output_filename}")
|
41
|
-
else
|
42
|
-
raise "Unable to find component #{component.name} in input source: path=#{fetch_return_value["path"]}"
|
27
|
+
when :directory
|
28
|
+
if Dir.exist?(fetch_return_value['path'])
|
29
|
+
unless Dir.exist?(@tempdir + "/#{subdirectory}/#{component.name}")
|
30
|
+
FileUtils.mkdir_p(@tempdir + "/#{subdirectory}/#{component.name}")
|
43
31
|
end
|
32
|
+
FileUtils.cp_r(fetch_return_value['path'] + '/.', @tempdir + "/#{subdirectory}/#{component.output_filename}")
|
33
|
+
else
|
34
|
+
raise "Unable to find component #{component.name} in input source: path=#{fetch_return_value['path']}"
|
35
|
+
end
|
36
|
+
when :file
|
37
|
+
if File.exist?(fetch_return_value['path'])
|
38
|
+
FileUtils.mkdir_p(@tempdir + "/#{subdirectory}")
|
39
|
+
FileUtils.cp_r(fetch_return_value['path'], @tempdir + "/#{subdirectory}/#{component.output_filename}")
|
40
|
+
else
|
41
|
+
raise "Unable to find component #{component.name} in input source: path=#{fetch_return_value['path']}"
|
42
|
+
end
|
44
43
|
end
|
45
|
-
|
46
44
|
end
|
47
45
|
|
48
|
-
def finalize(
|
49
|
-
if @options.key?(
|
50
|
-
if Dir.
|
51
|
-
FileUtils.cp_r(Dir.glob(@options[
|
46
|
+
def finalize(_manifest)
|
47
|
+
if @options.key?('local_directory')
|
48
|
+
if Dir.exist?(@options['local_directory'])
|
49
|
+
FileUtils.cp_r(Dir.glob(@options['local_directory'] + '/*'), @tempdir)
|
52
50
|
end
|
53
51
|
end
|
54
52
|
Dir.chdir(@origtempdir) do
|
55
|
-
`tar -cf - * | gzip -9nc >#{@options[
|
53
|
+
`tar -cf - * | gzip -9nc >#{@options['output']}`
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
59
|
-
|
60
|
-
|
57
|
+
# Needs to be finished/fixed
|
58
|
+
def output(_options, _directory, version, metadata, _output)
|
59
|
+
metadata.releases[version].components.each do |_component|
|
61
60
|
end
|
62
61
|
end
|
63
|
-
|
62
|
+
|
63
|
+
def cleanup
|
64
64
|
@cleanup.each do |path|
|
65
65
|
FileUtils.rmtree(path)
|
66
66
|
end
|