vanagon 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/git/basic_submodules.rb +53 -0
- data/lib/vanagon/component.rb +19 -13
- data/lib/vanagon/component/dsl.rb +6 -5
- data/lib/vanagon/component/source.rb +80 -53
- data/lib/vanagon/component/source/git.rb +129 -20
- data/lib/vanagon/component/source/http.rb +41 -73
- data/lib/vanagon/component/source/local.rb +105 -62
- data/lib/vanagon/platform/deb.rb +8 -0
- data/lib/vanagon/platform/rpm.rb +4 -0
- data/lib/vanagon/project/dsl.rb +3 -1
- data/lib/vanagon/utilities.rb +11 -54
- data/spec/fixtures/files/fake_file_ext.7z +0 -0
- data/spec/fixtures/files/fake_file_ext.bz +0 -0
- data/spec/fixtures/files/fake_file_ext.bz2 +0 -0
- data/spec/fixtures/files/fake_file_ext.cpio +0 -0
- data/spec/fixtures/files/fake_file_ext.gz +0 -0
- data/spec/fixtures/files/fake_file_ext.rar +0 -0
- data/spec/fixtures/files/fake_file_ext.tar +0 -0
- data/spec/fixtures/files/fake_file_ext.tar.bz2 +0 -0
- data/spec/fixtures/files/fake_file_ext.tar.xz +0 -0
- data/spec/fixtures/files/fake_file_ext.tbz +0 -0
- data/spec/fixtures/files/fake_file_ext.tbz2 +0 -0
- data/spec/fixtures/files/fake_file_ext.txz +0 -0
- data/spec/fixtures/files/fake_file_ext.xz +0 -0
- data/spec/fixtures/files/fake_file_ext.z +0 -0
- data/spec/lib/vanagon/component/source/git_spec.rb +25 -17
- data/spec/lib/vanagon/component/source/http_spec.rb +2 -24
- data/spec/lib/vanagon/component/source/{localsource_spec.rb → local_spec.rb} +8 -8
- data/spec/lib/vanagon/component/source_spec.rb +150 -67
- data/spec/lib/vanagon/platform_spec.rb +40 -21
- data/spec/lib/vanagon/project/dsl_spec.rb +28 -3
- data/spec/lib/vanagon/utilities_spec.rb +0 -44
- metadata +28 -13
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,33 +1,41 @@
|
|
1
1
|
require 'vanagon/component/source/git'
|
2
2
|
|
3
3
|
describe "Vanagon::Component::Source::Git" do
|
4
|
-
let
|
5
|
-
let
|
6
|
-
let
|
4
|
+
let(:klass) { Vanagon::Component::Source::Git }
|
5
|
+
let(:url) { 'git://github.com/puppetlabs/facter' }
|
6
|
+
let(:ref_tag) { 'refs/tags/2.2.0' }
|
7
|
+
let(:bad_sha) { 'FEEDBEEF' }
|
8
|
+
let(:workdir) { ENV["TMPDIR"] || "/tmp" }
|
9
|
+
|
10
|
+
after(:each) { %x(rm -rf #{workdir}/facter) }
|
11
|
+
|
12
|
+
describe "#initialize" do
|
13
|
+
it "raises error on initialization with an invalid repo" do
|
14
|
+
# this test has a spelling error for the git repo
|
15
|
+
# * this is on purpose *
|
16
|
+
expect { klass.new("#{url}l.git", ref: ref_tag, workdir: workdir) }
|
17
|
+
.to raise_error Vanagon::InvalidRepo
|
18
|
+
end
|
19
|
+
end
|
7
20
|
|
8
21
|
describe "#dirname" do
|
9
|
-
after(:each) { %x(rm -rf #{workdir}/facter) }
|
10
22
|
it "returns the name of the repo" do
|
11
|
-
git_source =
|
12
|
-
expect(git_source.dirname)
|
23
|
+
git_source = klass.new(url, ref: ref_tag, workdir: workdir)
|
24
|
+
expect(git_source.dirname)
|
25
|
+
.to eq('facter')
|
13
26
|
end
|
14
27
|
|
15
28
|
it "returns the name of the repo and strips .git" do
|
16
|
-
git_source =
|
17
|
-
expect(git_source.dirname)
|
29
|
+
git_source = klass.new("#{url}.git", ref: ref_tag, workdir: workdir)
|
30
|
+
expect(git_source.dirname)
|
31
|
+
.to eq('facter')
|
18
32
|
end
|
19
33
|
end
|
20
34
|
|
21
35
|
describe "#fetch" do
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
git_source = Vanagon::Component::Source::Git.new("#{url}l.git", ref, workdir)
|
26
|
-
expect { git_source.fetch }.to raise_error(RuntimeError, "git clone #{url}l.git failed")
|
27
|
-
end
|
28
|
-
it "raises error on checkout failure" do
|
29
|
-
git_source = Vanagon::Component::Source::Git.new("#{url}", "999.9.9", workdir)
|
30
|
-
expect { git_source.fetch }.to raise_error(RuntimeError, "git checkout 999.9.9 failed")
|
36
|
+
it "raises an error on checkout failure with a bad SHA" do
|
37
|
+
expect { klass.new("#{url}", ref: bad_sha, workdir: workdir).fetch }
|
38
|
+
.to raise_error Vanagon::CheckoutFailed
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
@@ -14,39 +14,17 @@ describe "Vanagon::Component::Source::Http" do
|
|
14
14
|
|
15
15
|
describe "#dirname" do
|
16
16
|
it "returns the name of the tarball, minus extension for archives" do
|
17
|
-
http_source = Vanagon::Component::Source::Http.new(tar_url, md5sum, workdir)
|
17
|
+
http_source = Vanagon::Component::Source::Http.new(tar_url, sum: md5sum, workdir: workdir)
|
18
18
|
expect(http_source).to receive(:download).and_return(tar_filename)
|
19
19
|
http_source.fetch
|
20
20
|
expect(http_source.dirname).to eq(tar_dirname)
|
21
21
|
end
|
22
22
|
|
23
23
|
it "returns the current directory for non-archive files" do
|
24
|
-
http_source = Vanagon::Component::Source::Http.new(plaintext_url, md5sum, workdir)
|
24
|
+
http_source = Vanagon::Component::Source::Http.new(plaintext_url, sum: md5sum, workdir: workdir)
|
25
25
|
expect(http_source).to receive(:download).and_return(plaintext_filename)
|
26
26
|
http_source.fetch
|
27
27
|
expect(http_source.dirname).to eq(plaintext_dirname)
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
31
|
-
describe "#get_extension" do
|
32
|
-
it "returns the extension for valid extensions" do
|
33
|
-
Vanagon::Component::Source::Http::ARCHIVE_EXTENSIONS.each do |ext|
|
34
|
-
filename = "#{file_base}#{ext}"
|
35
|
-
url = File.join(base_url, filename)
|
36
|
-
http_source = Vanagon::Component::Source::Http.new(url, md5sum, workdir)
|
37
|
-
expect(http_source).to receive(:download).and_return(filename)
|
38
|
-
http_source.fetch
|
39
|
-
expect(http_source.get_extension).to eq(ext)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
it "is able to download non archive extensions" do
|
44
|
-
["gpg.txt", "foo.service", "configi.json", "config.repo.txt", "noextensionfile"].each do |filename|
|
45
|
-
url = File.join(base_url, filename)
|
46
|
-
http_source = Vanagon::Component::Source::Http.new(url, md5sum, workdir)
|
47
|
-
expect(http_source).to receive(:download).and_return(filename)
|
48
|
-
http_source.fetch
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
30
|
end
|
@@ -8,7 +8,7 @@ describe "Vanagon::Component::Source::File" do
|
|
8
8
|
|
9
9
|
describe "#fetch" do
|
10
10
|
it "puts the source file in to the workdir" do
|
11
|
-
file = Vanagon::Component::Source::Local.new(plaintext_filename, workdir)
|
11
|
+
file = Vanagon::Component::Source::Local.new(plaintext_filename, workdir: workdir)
|
12
12
|
file.fetch
|
13
13
|
expect(File).to exist("#{workdir}/fake_file.txt")
|
14
14
|
end
|
@@ -16,26 +16,26 @@ describe "Vanagon::Component::Source::File" do
|
|
16
16
|
|
17
17
|
describe "#dirname" do
|
18
18
|
it "returns the name of the tarball, minus extension for archives" do
|
19
|
-
file = Vanagon::Component::Source::Local.new(tar_filename, workdir)
|
19
|
+
file = Vanagon::Component::Source::Local.new(tar_filename, workdir: workdir)
|
20
20
|
file.fetch
|
21
21
|
expect(file.dirname).to eq("fake_dir")
|
22
22
|
end
|
23
23
|
|
24
24
|
it "returns the current directory for non-archive files" do
|
25
|
-
file = Vanagon::Component::Source::Local.new(plaintext_filename, workdir)
|
25
|
+
file = Vanagon::Component::Source::Local.new(plaintext_filename, workdir: workdir)
|
26
26
|
file.fetch
|
27
27
|
expect(file.dirname).to eq("./")
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
describe "#
|
31
|
+
describe "#extension" do
|
32
32
|
it "returns the extension for valid extensions" do
|
33
|
-
Vanagon::Component::Source::Local
|
33
|
+
Vanagon::Component::Source::Local.archive_extensions.each do |ext|
|
34
34
|
filename = "#{file_base}#{ext}"
|
35
|
-
file = Vanagon::Component::Source::Local.new(filename, workdir)
|
35
|
+
file = Vanagon::Component::Source::Local.new(filename, workdir: workdir)
|
36
36
|
file.fetch
|
37
|
-
expect(file.
|
37
|
+
expect(file.extension).to eq(ext)
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
|
-
end
|
41
|
+
end
|
@@ -1,99 +1,182 @@
|
|
1
1
|
require 'vanagon/component/source'
|
2
2
|
|
3
3
|
describe "Vanagon::Component::Source" do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
let
|
10
|
-
let
|
11
|
-
|
12
|
-
let
|
13
|
-
let
|
14
|
-
let
|
15
|
-
let
|
16
|
-
|
17
|
-
let
|
18
|
-
let
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
it "
|
38
|
-
expect
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
4
|
+
let(:klass) { Vanagon::Component::Source }
|
5
|
+
before(:each) { klass.rewrite_rules.clear }
|
6
|
+
|
7
|
+
describe ".source" do
|
8
|
+
let(:unrecognized_uri) { "abcd://things" }
|
9
|
+
let(:unrecognized_scheme) { "abcd" }
|
10
|
+
let(:invalid_scheme) { "abcd|things" }
|
11
|
+
|
12
|
+
let(:public_git) { "git://github.com/abcd/things" }
|
13
|
+
let(:private_git) { "git@github.com:abcd/things" }
|
14
|
+
let(:http_git) { "http://github.com/abcd/things" }
|
15
|
+
let(:https_git) { "https://github.com/abcd/things" }
|
16
|
+
|
17
|
+
let(:http_url) { "http://abcd/things" }
|
18
|
+
let(:https_url) { "https://abcd/things" }
|
19
|
+
|
20
|
+
let(:file_url) { "file://things" }
|
21
|
+
|
22
|
+
let(:ref) { "cafebeef" }
|
23
|
+
let(:sum) { "abcd1234" }
|
24
|
+
let(:workdir) { "/tmp" }
|
25
|
+
|
26
|
+
let(:original_git_url) { "git://things.and.stuff/foo-bar.git" }
|
27
|
+
let(:rewritten_git_url) { "git://things.end.stuff/foo-ber.git" }
|
28
|
+
|
29
|
+
let(:original_http_url) { "http://things.and.stuff/foo.tar.gz" }
|
30
|
+
let(:rewritten_http_url) { "http://buildsources.delivery.puppetlabs.net/foo.tar.gz" }
|
31
|
+
|
32
|
+
it "fails on unrecognized URI schemes" do
|
33
|
+
expect { klass.source(unrecognized_uri, workdir: workdir) }
|
34
|
+
.to raise_error(Vanagon::Error)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "fails on invalid URIs" do
|
38
|
+
expect { klass.source(invalid_scheme, workdir: workdir) }
|
39
|
+
.to raise_error(URI::InvalidURIError)
|
40
|
+
end
|
41
|
+
|
42
|
+
context "takes a Git repo" do
|
43
|
+
before do
|
44
|
+
allow_any_instance_of(Vanagon::Component::Source::Git)
|
45
|
+
.to receive(:valid_remote?)
|
46
|
+
.and_return(true)
|
47
|
+
|
48
|
+
allow(Vanagon::Component::Source::Git)
|
49
|
+
.to receive(:valid_remote?)
|
50
|
+
.and_return(true)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns a Git object for git@ triplet repositories" do
|
54
|
+
expect(klass.source(private_git, ref: ref, workdir: workdir).class)
|
55
|
+
.to eq Vanagon::Component::Source::Git
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns a Git object for git:// repositories" do
|
59
|
+
expect(klass.source(public_git, ref: ref, workdir: workdir).class)
|
60
|
+
.to eq Vanagon::Component::Source::Git
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns a Git object for http:// repositories" do
|
64
|
+
expect(klass.source(http_git, ref: ref, workdir: workdir).class)
|
65
|
+
.to eq Vanagon::Component::Source::Git
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns a Git object for https:// repositories" do
|
69
|
+
expect(klass.source(https_git, ref: ref, workdir: workdir).class)
|
70
|
+
.to eq Vanagon::Component::Source::Git
|
71
|
+
end
|
72
|
+
|
73
|
+
it "rewrites git:// URLs" do
|
74
|
+
proc_rule = Proc.new { |url| url.gsub('a', 'e') }
|
75
|
+
klass.register_rewrite_rule('git', proc_rule)
|
76
|
+
# Vanagon::Component::Source::Git#url returns a URI object
|
77
|
+
# so to check its value, we cast it to a simple string. It's
|
78
|
+
# hacky for sure, but seems less diagreeable than mangling the
|
79
|
+
# return value in the class itself.
|
80
|
+
expect(klass.source(original_git_url, ref: ref, workdir: workdir).url.to_s)
|
81
|
+
.to eq rewritten_git_url
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "takes a HTTP/HTTPS file" do
|
86
|
+
before do
|
87
|
+
allow_any_instance_of(Vanagon::Component::Source::Http)
|
88
|
+
.to receive(:valid_url?)
|
89
|
+
.and_return(true)
|
90
|
+
|
91
|
+
allow(Vanagon::Component::Source::Http)
|
92
|
+
.to receive(:valid_url?)
|
93
|
+
.and_return(true)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "returns an object of the correct type for http:// URLS" do
|
97
|
+
expect(klass.source(http_url, sum: sum, workdir: workdir).class)
|
98
|
+
.to equal(Vanagon::Component::Source::Http)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns an object of the correct type for https:// URLS" do
|
102
|
+
expect(klass.source(https_url, sum: sum, workdir: workdir).class)
|
103
|
+
.to equal(Vanagon::Component::Source::Http)
|
104
|
+
end
|
105
|
+
|
106
|
+
before do
|
107
|
+
klass.register_rewrite_rule 'http',
|
108
|
+
'http://buildsources.delivery.puppetlabs.net'
|
109
|
+
end
|
110
|
+
it "applies rewrite rules to HTTP URLs" do
|
111
|
+
expect(klass.source(original_http_url, sum: sum, workdir: workdir).url)
|
112
|
+
.to eq(rewritten_http_url)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "takes a local file" do
|
117
|
+
before do
|
118
|
+
allow_any_instance_of(Vanagon::Component::Source::Local)
|
119
|
+
.to receive(:valid_file?)
|
120
|
+
.and_return(true)
|
121
|
+
|
122
|
+
allow(Vanagon::Component::Source::Local)
|
123
|
+
.to receive(:valid_file?)
|
124
|
+
.and_return(true)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "returns an object of the correct type for file:// URLS" do
|
128
|
+
expect(klass.source(file_url, sum: sum, workdir: workdir).class)
|
129
|
+
.to eq Vanagon::Component::Source::Local
|
130
|
+
end
|
57
131
|
end
|
58
132
|
end
|
59
133
|
|
60
|
-
describe "
|
134
|
+
describe ".rewrite" do
|
61
135
|
let(:simple_rule) { Proc.new {|url| url.gsub('a', 'e') } }
|
62
136
|
let(:complex_rule) do
|
63
|
-
Proc.new
|
137
|
+
Proc.new do |url|
|
64
138
|
match = url.match(/github.com\/(.*)$/)
|
65
139
|
"git://github.delivery.puppetlabs.net/#{match[1].gsub('/', '-')}" if match
|
66
|
-
|
140
|
+
end
|
67
141
|
end
|
68
142
|
|
69
143
|
it 'replaces the first section of a url with a string if string is given' do
|
70
|
-
|
71
|
-
|
144
|
+
klass.register_rewrite_rule('http', 'http://buildsources.delivery.puppetlabs.net')
|
145
|
+
|
146
|
+
expect(klass.rewrite('http://things.and.stuff/foo.tar.gz', 'http'))
|
147
|
+
.to eq('http://buildsources.delivery.puppetlabs.net/foo.tar.gz')
|
72
148
|
end
|
73
149
|
|
74
150
|
it 'applies the rule to the url if a proc is given as the rule' do
|
75
|
-
|
76
|
-
|
151
|
+
klass.register_rewrite_rule('http', simple_rule)
|
152
|
+
|
153
|
+
expect(klass.rewrite('http://things.and.stuff/foo.tar.gz', 'http'))
|
154
|
+
.to eq('http://things.end.stuff/foo.ter.gz')
|
77
155
|
end
|
78
156
|
|
79
157
|
it 'applies the rule to the url if a proc is given as the rule' do
|
80
|
-
|
81
|
-
|
158
|
+
klass.register_rewrite_rule('git', complex_rule)
|
159
|
+
|
160
|
+
expect(klass.rewrite('git://github.com/puppetlabs/facter', 'git'))
|
161
|
+
.to eq('git://github.delivery.puppetlabs.net/puppetlabs-facter')
|
82
162
|
end
|
83
163
|
end
|
84
164
|
|
85
|
-
describe "
|
165
|
+
describe ".register_rewrite_rule" do
|
86
166
|
it 'only accepts Proc and String as rule types' do
|
87
|
-
expect{
|
167
|
+
expect { klass.register_rewrite_rule('http', 5) }
|
168
|
+
.to raise_error(Vanagon::Error)
|
88
169
|
end
|
89
170
|
|
90
171
|
it 'rejects invalid protocols' do
|
91
|
-
expect{
|
172
|
+
expect { klass.register_rewrite_rule('gopher', 'abcd') }
|
173
|
+
.to raise_error Vanagon::Error
|
92
174
|
end
|
93
175
|
|
176
|
+
before { klass.register_rewrite_rule('http', 'http://buildsources.delivery.puppetlabs.net') }
|
94
177
|
it 'registers the rule for the given protocol' do
|
95
|
-
|
96
|
-
|
178
|
+
expect(klass.rewrite_rules)
|
179
|
+
.to eq({'http' => 'http://buildsources.delivery.puppetlabs.net'})
|
97
180
|
end
|
98
181
|
end
|
99
182
|
end
|
@@ -4,31 +4,42 @@ describe "Vanagon::Platform" do
|
|
4
4
|
let(:platforms) do
|
5
5
|
[
|
6
6
|
{
|
7
|
-
:name
|
8
|
-
:os_name
|
9
|
-
:os_version
|
10
|
-
:architecture
|
11
|
-
:output_dir
|
12
|
-
:output_dir_with_target
|
13
|
-
:
|
7
|
+
:name => "debian-6-i386",
|
8
|
+
:os_name => "debian",
|
9
|
+
:os_version => "6",
|
10
|
+
:architecture => "i386",
|
11
|
+
:output_dir => "deb/lucid/",
|
12
|
+
:output_dir_with_target => "deb/lucid/thing",
|
13
|
+
:output_dir_empty_string => "deb/lucid/",
|
14
|
+
:block => %Q[
|
15
|
+
platform "debian-6-i386" do |plat|
|
16
|
+
plat.codename "lucid"
|
17
|
+
end ],
|
14
18
|
},
|
15
19
|
{
|
16
|
-
:name
|
17
|
-
:os_name
|
18
|
-
:os_version
|
19
|
-
:architecture
|
20
|
-
:output_dir
|
21
|
-
:output_dir_with_target
|
22
|
-
:
|
20
|
+
:name => "el-5-i386",
|
21
|
+
:os_name => "el",
|
22
|
+
:os_version => "5",
|
23
|
+
:architecture => "i386",
|
24
|
+
:output_dir => "el/5/products/i386",
|
25
|
+
:output_dir_with_target => "el/5/thing/i386",
|
26
|
+
:output_dir_empty_string => "el/5/i386",
|
27
|
+
:block => %Q[ platform "el-5-i386" do |plat| end ],
|
23
28
|
},
|
24
29
|
{
|
25
|
-
:name
|
26
|
-
:os_name
|
27
|
-
:os_version
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
30
|
+
:name => "debian-6-i386",
|
31
|
+
:os_name => "debian",
|
32
|
+
:os_version => "6",
|
33
|
+
:codename => "lucid",
|
34
|
+
:architecture => "i386",
|
35
|
+
:output_dir => "updated/output",
|
36
|
+
:output_dir_with_target => "updated/output",
|
37
|
+
:output_dir_empty_string => "updated/output",
|
38
|
+
:block => %Q[
|
39
|
+
platform "debian-6-i386" do |plat|
|
40
|
+
plat.codename "lucid"
|
41
|
+
plat.output_dir "updated/output"
|
42
|
+
end ],
|
32
43
|
},
|
33
44
|
]
|
34
45
|
end
|
@@ -67,6 +78,14 @@ describe "Vanagon::Platform" do
|
|
67
78
|
expect(cur_plat._platform.output_dir('thing')).to eq(plat[:output_dir_with_target])
|
68
79
|
end
|
69
80
|
end
|
81
|
+
|
82
|
+
it "does the right thing with empty strings" do
|
83
|
+
platforms.each do |plat|
|
84
|
+
cur_plat = Vanagon::Platform::DSL.new(plat[:name])
|
85
|
+
cur_plat.instance_eval(plat[:block])
|
86
|
+
expect(cur_plat._platform.output_dir('')).to eq(plat[:output_dir_empty_string])
|
87
|
+
end
|
88
|
+
end
|
70
89
|
end
|
71
90
|
|
72
91
|
describe "#architecture" do
|