plow 0.1.0 → 1.0.0
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.
- data/.gitignore +2 -2
- data/.yardopts +4 -2
- data/README.markdown +86 -41
- data/Rakefile +34 -28
- data/VERSION +1 -1
- data/bin/plow +1 -7
- data/bin/plow1.9 +1 -7
- data/doc/HISTORY.markdown +41 -0
- data/doc/ROAD-MAP.markdown +12 -0
- data/{SECURITY → doc/SECURITY.markdown} +4 -3
- data/lib/plow/application.rb +33 -1
- data/lib/plow/binding_struct.rb +38 -2
- data/lib/plow/core_ext/object.rb +9 -1
- data/lib/plow/dependencies.rb +123 -0
- data/lib/plow/errors.rb +8 -0
- data/lib/plow/generator.rb +88 -6
- data/lib/plow/strategy/ubuntu_hardy.rb +316 -0
- data/lib/plow.rb +5 -11
- data/spec/plow/application_spec.rb +2 -2
- data/spec/plow/binding_struct_spec.rb +1 -1
- data/spec/plow/dependencies_spec.rb +175 -0
- data/spec/plow/errors_spec.rb +1 -1
- data/spec/plow/generator_spec.rb +6 -6
- data/spec/plow/strategy/{ubuntu_hardy/user_home_web_app_spec.rb → ubuntu_hardy_spec.rb} +96 -96
- data/spec/plow_spec.rb +2 -2
- data/spec/spec_helper.rb +0 -24
- metadata +25 -13
- data/HISTORY +0 -39
- data/ROAD-MAP +0 -10
- data/lib/plow/strategy/ubuntu_hardy/templates/README.txt +0 -19
- data/lib/plow/strategy/ubuntu_hardy/user_home_web_app.rb +0 -304
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Sobol
|
@@ -9,9 +9,19 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-11-21 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: jeweler
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.3.0
|
24
|
+
version:
|
15
25
|
- !ruby/object:Gem::Dependency
|
16
26
|
name: rspec
|
17
27
|
type: :development
|
@@ -20,7 +30,7 @@ dependencies:
|
|
20
30
|
requirements:
|
21
31
|
- - "="
|
22
32
|
- !ruby/object:Gem::Version
|
23
|
-
version: 1.2.
|
33
|
+
version: 1.2.9
|
24
34
|
version:
|
25
35
|
- !ruby/object:Gem::Dependency
|
26
36
|
name: yard
|
@@ -30,7 +40,7 @@ dependencies:
|
|
30
40
|
requirements:
|
31
41
|
- - "="
|
32
42
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
43
|
+
version: 0.4.0
|
34
44
|
version:
|
35
45
|
- !ruby/object:Gem::Dependency
|
36
46
|
name: bluecloth
|
@@ -55,31 +65,32 @@ extra_rdoc_files:
|
|
55
65
|
files:
|
56
66
|
- .gitignore
|
57
67
|
- .yardopts
|
58
|
-
- HISTORY
|
59
68
|
- LICENSE
|
60
69
|
- README.markdown
|
61
|
-
- ROAD-MAP
|
62
70
|
- Rakefile
|
63
|
-
- SECURITY
|
64
71
|
- VERSION
|
65
72
|
- bin/plow
|
66
73
|
- bin/plow1.9
|
74
|
+
- doc/HISTORY.markdown
|
75
|
+
- doc/ROAD-MAP.markdown
|
76
|
+
- doc/SECURITY.markdown
|
67
77
|
- lib/plow.rb
|
68
78
|
- lib/plow/application.rb
|
69
79
|
- lib/plow/binding_struct.rb
|
70
80
|
- lib/plow/core_ext/object.rb
|
81
|
+
- lib/plow/dependencies.rb
|
71
82
|
- lib/plow/errors.rb
|
72
83
|
- lib/plow/generator.rb
|
73
|
-
- lib/plow/strategy/ubuntu_hardy
|
84
|
+
- lib/plow/strategy/ubuntu_hardy.rb
|
74
85
|
- lib/plow/strategy/ubuntu_hardy/templates/apache2-vhost.conf
|
75
|
-
- lib/plow/strategy/ubuntu_hardy/user_home_web_app.rb
|
76
86
|
- spec/fixtures/passwd.txt
|
77
87
|
- spec/fixtures/vhost.conf
|
78
88
|
- spec/plow/application_spec.rb
|
79
89
|
- spec/plow/binding_struct_spec.rb
|
90
|
+
- spec/plow/dependencies_spec.rb
|
80
91
|
- spec/plow/errors_spec.rb
|
81
92
|
- spec/plow/generator_spec.rb
|
82
|
-
- spec/plow/strategy/
|
93
|
+
- spec/plow/strategy/ubuntu_hardy_spec.rb
|
83
94
|
- spec/plow_spec.rb
|
84
95
|
- spec/spec.opts
|
85
96
|
- spec/spec_helper.rb
|
@@ -94,9 +105,9 @@ require_paths:
|
|
94
105
|
- lib
|
95
106
|
required_ruby_version: !ruby/object:Gem::Requirement
|
96
107
|
requirements:
|
97
|
-
- -
|
108
|
+
- - ~>
|
98
109
|
- !ruby/object:Gem::Version
|
99
|
-
version:
|
110
|
+
version: 1.9.1
|
100
111
|
version:
|
101
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
113
|
requirements:
|
@@ -114,8 +125,9 @@ summary: Plows the fertile soil of your filesystem into neatly organized plots o
|
|
114
125
|
test_files:
|
115
126
|
- spec/plow/application_spec.rb
|
116
127
|
- spec/plow/binding_struct_spec.rb
|
128
|
+
- spec/plow/dependencies_spec.rb
|
117
129
|
- spec/plow/errors_spec.rb
|
118
130
|
- spec/plow/generator_spec.rb
|
119
|
-
- spec/plow/strategy/
|
131
|
+
- spec/plow/strategy/ubuntu_hardy_spec.rb
|
120
132
|
- spec/plow_spec.rb
|
121
133
|
- spec/spec_helper.rb
|
data/HISTORY
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
== 1.0.0 released on Oct. ??th 2009
|
2
|
-
|
3
|
-
* bin/plow and bin/plow1.9
|
4
|
-
* Two ruby 1.9.1 compatible command-line executables.
|
5
|
-
* Combined with Plow::Application, the entire application logic is decoupled from the plow generator library.
|
6
|
-
* lib/plow.rb
|
7
|
-
* Handles load-time errors gracefully for the user
|
8
|
-
* Specifies library-wide load order
|
9
|
-
* Maintains the library version in memory
|
10
|
-
* lib/plow/application.rb
|
11
|
-
* decoupled argument parsing from the generator
|
12
|
-
* displays usage for the command-line executables
|
13
|
-
* handles run-time errors gracefully for the user
|
14
|
-
* starts the generator
|
15
|
-
* Combined with the executables, the entire application logic is decoupled from the plow generator library.
|
16
|
-
* lib/plow/generator.rb
|
17
|
-
* main controller
|
18
|
-
* maintains the shared state data including a strategy object
|
19
|
-
* passes execution to the strategy object
|
20
|
-
* provides shared logic for outputting messages to the user
|
21
|
-
* provides shared logic for executing shell commands
|
22
|
-
* provides shared logic for evaluating ERB template files
|
23
|
-
* lib/plow/binding_struct.rb
|
24
|
-
* helper object that converts a Hash object into instances variables, which then is used as the binding object for ERB
|
25
|
-
* lib/plow/core_ext/object.rb
|
26
|
-
* dynamically adds a #blank? method to all objects (copied directly from the active_support library)
|
27
|
-
* lib/plow/errors.rb
|
28
|
-
* defines a handful of run-time error classes
|
29
|
-
* lib/plow/strategy/ubuntu_hardy/user_home_web_app.rb
|
30
|
-
* userhome-based strategy for generating a system-wide web-site structure for the Linux Ubuntu Hardy 8.04.3 LTS operating system
|
31
|
-
* understanding and managing the security implications of web server of virtual hosts
|
32
|
-
* lib/plow/strategy/ubuntu_hardy/templates/apache2-vhost.conf
|
33
|
-
* a simple apache2 virtual host ERB template file for the Linux Ubuntu Hardy 8.04.3 LTS operating system
|
34
|
-
* lib/plow/strategy/ubuntu_hardy/templates/README.txt
|
35
|
-
* a README file that explains the generated web-site directory structure and files
|
36
|
-
* Rakefile
|
37
|
-
* incorporating deployments with jeweler and gemcutter. the combination is extremely powerful and seems alot more practical than maintaining my own rubygem server.
|
38
|
-
|
39
|
-
* inviting others to review my code and submitting patches! long live the open-source community!
|
data/ROAD-MAP
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
== Improvements
|
2
|
-
|
3
|
-
The obvious potential improvements are ported the package to additional operating systems and web servers. This would require minimal, but completely worth-while structure changes Plow::Generator class.
|
4
|
-
|
5
|
-
* Move all system administrative logic to model classes and redesign the class around the builder pattern to allow to construct user-determined classes dynamically at run-time
|
6
|
-
* Move all data validations to the same model classes or to new classes entirely, depending on the needs at the time
|
7
|
-
* More specific data validations
|
8
|
-
* Handing of more complex web application structures
|
9
|
-
* automatic deployment of web applications from github
|
10
|
-
* better argument parsing and including option flags as needed
|
@@ -1,19 +0,0 @@
|
|
1
|
-
$ tree sites/
|
2
|
-
sites/
|
3
|
-
|-- README
|
4
|
-
`-- example.ryansobol.com
|
5
|
-
|-- log
|
6
|
-
| `-- apache2
|
7
|
-
| |-- access.log
|
8
|
-
| `-- error.log
|
9
|
-
`-- public
|
10
|
-
`-- index.html
|
11
|
-
|
12
|
-
4 directories, 4 files
|
13
|
-
|
14
|
-
$ ls -hal sites/example.ryansobol.com/log/apache2/
|
15
|
-
total 196K
|
16
|
-
drwxr-x--- 2 root GROUP 4.0K Sep 5 03:11 .
|
17
|
-
drwxr-xr-x 3 USER GROUP 4.0K Sep 5 03:09 ..
|
18
|
-
-rw-r----- 1 root GROUP 136K Sep 9 11:10 access.log
|
19
|
-
-rw-r----- 1 root GROUP 48K Sep 9 09:06 error.log
|
@@ -1,304 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
class Plow
|
4
|
-
class Strategy
|
5
|
-
class UbuntuHardy
|
6
|
-
|
7
|
-
##
|
8
|
-
# Plowing strategy, compatible with the Linux Ubuntu Hardy Heron distribution, for generating
|
9
|
-
# a web-site within a user home directory.
|
10
|
-
class UserHomeWebApp
|
11
|
-
attr_reader :context, :users_file_path, :vhost_file_name, :vhost_file_path, :vhost_template_file_path
|
12
|
-
attr_reader :user_home_path, :sites_home_path, :app_root_path, :app_public_path, :app_log_path
|
13
|
-
|
14
|
-
##
|
15
|
-
# @param [Plow::Generator] context A context reference to the generator controller. (i.e. Strategy pattern)
|
16
|
-
#
|
17
|
-
# @example
|
18
|
-
# class Plow
|
19
|
-
# class Generator
|
20
|
-
# def initialize
|
21
|
-
# @strategy = Plow::Strategy::UbuntuHardy::UserHomeWebApp.new(self)
|
22
|
-
# end
|
23
|
-
# end
|
24
|
-
# end
|
25
|
-
def initialize(context)
|
26
|
-
@context = context
|
27
|
-
@users_file_path = "/etc/passwd"
|
28
|
-
@vhost_file_name = "#{context.site_name}.conf"
|
29
|
-
@vhost_file_path = "/etc/apache2/sites-available/#{vhost_file_name}"
|
30
|
-
|
31
|
-
@vhost_template_file_path = "#{File.dirname(__FILE__)}/templates/apache2-vhost.conf"
|
32
|
-
end
|
33
|
-
|
34
|
-
##
|
35
|
-
# Begins executing this strategy. In addition to the following exceptions, this method may
|
36
|
-
# also raise exceptions found in private methods of this class.
|
37
|
-
#
|
38
|
-
# @raise [Plow::AppRootAlreadyExistsError] Raised if the web-app root path directory
|
39
|
-
# aleady exists
|
40
|
-
# @raise [Plow::ConfigFileAlreadyExistsError] Raised if a apache2 vhost configuration file
|
41
|
-
# cannot be found
|
42
|
-
def execute
|
43
|
-
if user_exists?
|
44
|
-
say "existing #{context.user_name} user"
|
45
|
-
else
|
46
|
-
say "creating #{context.user_name} user"
|
47
|
-
create_user
|
48
|
-
end
|
49
|
-
|
50
|
-
if user_home_exists?
|
51
|
-
say "existing #{user_home_path}"
|
52
|
-
else
|
53
|
-
say "creating #{user_home_path}"
|
54
|
-
create_user_home
|
55
|
-
end
|
56
|
-
|
57
|
-
if sites_home_exists?
|
58
|
-
say "existing #{sites_home_path}"
|
59
|
-
else
|
60
|
-
say "creating #{sites_home_path}"
|
61
|
-
create_sites_home
|
62
|
-
end
|
63
|
-
|
64
|
-
if app_root_exists?
|
65
|
-
raise(Plow::AppRootAlreadyExistsError, app_root_path)
|
66
|
-
else
|
67
|
-
say "creating #{app_root_path}"
|
68
|
-
create_app_root
|
69
|
-
end
|
70
|
-
|
71
|
-
@app_public_path = "#{app_root_path}/public"
|
72
|
-
say "creating #{@app_public_path}"
|
73
|
-
create_app_public
|
74
|
-
|
75
|
-
@app_log_path = "#{app_root_path}/log"
|
76
|
-
say "creating #{app_log_path}"
|
77
|
-
create_app_logs
|
78
|
-
|
79
|
-
if vhost_config_exists?
|
80
|
-
raise(Plow::ConfigFileAlreadyExistsError, vhost_file_path)
|
81
|
-
else
|
82
|
-
say "creating #{vhost_file_path}"
|
83
|
-
create_vhost_config
|
84
|
-
end
|
85
|
-
|
86
|
-
say "installing #{vhost_file_path}"
|
87
|
-
install_vhost_config
|
88
|
-
end
|
89
|
-
|
90
|
-
############################################################################################################
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
##
|
95
|
-
# Proxy method to +Plow::Generator#say+
|
96
|
-
# @param [String] message A user output message
|
97
|
-
def say(message)
|
98
|
-
context.say(message)
|
99
|
-
end
|
100
|
-
|
101
|
-
##
|
102
|
-
# Proxy method to +Plow::Generator#shell+
|
103
|
-
# @param [String] commands Shell commands with multi-line support.
|
104
|
-
def shell(commands)
|
105
|
-
context.shell(commands)
|
106
|
-
end
|
107
|
-
|
108
|
-
##
|
109
|
-
# Reads the file at +users_file_path+ and yields each user iteratively.
|
110
|
-
#
|
111
|
-
#
|
112
|
-
# @yield [Hash] Each user account
|
113
|
-
# @example
|
114
|
-
# users do |user|
|
115
|
-
# user[:name] #=> [String] The name
|
116
|
-
# user[:password] #=> [String] The bogus password
|
117
|
-
# user[:id] #=> [Number] The uid number
|
118
|
-
# user[:group_ip] #=> [Number] The gid number
|
119
|
-
# user[:info] #=> [String] General account info
|
120
|
-
# user[:home_path] #=> [String] The path to the home directory
|
121
|
-
# user[:shell_path] #=> [String] The path to the default shell
|
122
|
-
# end
|
123
|
-
def users(&block)
|
124
|
-
File.readlines(users_file_path).each do |user_line|
|
125
|
-
user_line = user_line.chomp.split(':')
|
126
|
-
user = {
|
127
|
-
:name => user_line[0],
|
128
|
-
:password => user_line[1],
|
129
|
-
:id => user_line[2].to_i,
|
130
|
-
:group_id => user_line[3].to_i,
|
131
|
-
:info => user_line[4],
|
132
|
-
:home_path => user_line[5],
|
133
|
-
:shell_path => user_line[6]
|
134
|
-
}
|
135
|
-
yield user
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
############################################################################################################
|
140
|
-
|
141
|
-
##
|
142
|
-
# Determines if the context.user_name already exists or not
|
143
|
-
#
|
144
|
-
# @return [Boolean] +true+ if found otherwise +false+
|
145
|
-
# @raise [Plow::ReservedSystemUserNameError] Raised if the +context.user_name+ is a reserved
|
146
|
-
# system user name
|
147
|
-
def user_exists?
|
148
|
-
users do |user|
|
149
|
-
if user[:name] == context.user_name
|
150
|
-
unless user[:id] >= 1000 && user[:id] != 65534
|
151
|
-
raise(Plow::ReservedSystemUserNameError, context.user_name)
|
152
|
-
end
|
153
|
-
return true
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
return false
|
158
|
-
end
|
159
|
-
|
160
|
-
##
|
161
|
-
# Creates a system user account for +context.user_name+
|
162
|
-
def create_user
|
163
|
-
shell "adduser #{context.user_name}"
|
164
|
-
end
|
165
|
-
|
166
|
-
############################################################################################################
|
167
|
-
|
168
|
-
##
|
169
|
-
# Determines if a home path for the +context.user_name+ already exists
|
170
|
-
#
|
171
|
-
# @return [Boolean] +true+ if the path already exists, otherwise +false+
|
172
|
-
# @raise [Plow::SystemUserNameNotFoundError] Raised if the +context.user_name+ is not found
|
173
|
-
# even after it has been created
|
174
|
-
def user_home_exists?
|
175
|
-
users do |user|
|
176
|
-
if user[:name] == context.user_name
|
177
|
-
@user_home_path = user[:home_path]
|
178
|
-
return Dir.exists?(user[:home_path])
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
raise(Plow::SystemUserNameNotFoundError, context.user_name)
|
183
|
-
end
|
184
|
-
|
185
|
-
##
|
186
|
-
# Creates a +user_home_path+
|
187
|
-
def create_user_home
|
188
|
-
shell <<-RUN
|
189
|
-
mkdir #{user_home_path}
|
190
|
-
chown #{context.user_name}:#{context.user_name} #{user_home_path}
|
191
|
-
RUN
|
192
|
-
end
|
193
|
-
|
194
|
-
############################################################################################################
|
195
|
-
|
196
|
-
##
|
197
|
-
# Determines if the +sites_home_path+ already exists. As a side-effect, also correctly
|
198
|
-
# sets the +@sites_home_path+ variable.
|
199
|
-
# @return [Boolean] +true+ if the path already exists, otherwise +false+
|
200
|
-
def sites_home_exists?
|
201
|
-
@sites_home_path = "#{user_home_path}/sites"
|
202
|
-
Dir.exists?(sites_home_path)
|
203
|
-
end
|
204
|
-
|
205
|
-
##
|
206
|
-
# Creates the +site_home_path+
|
207
|
-
def create_sites_home
|
208
|
-
shell <<-RUN
|
209
|
-
mkdir #{sites_home_path}
|
210
|
-
chown #{context.user_name}:#{context.user_name} #{sites_home_path}
|
211
|
-
RUN
|
212
|
-
end
|
213
|
-
|
214
|
-
############################################################################################################
|
215
|
-
|
216
|
-
##
|
217
|
-
# Determines if the +app_root_path+ already exists. As a side-effect, also correctly
|
218
|
-
# sets the +@app_root_path+ variable.
|
219
|
-
# @return [Boolean] +true+ if the path exists, otherwise +false+
|
220
|
-
def app_root_exists?
|
221
|
-
@app_root_path = "#{sites_home_path}/#{context.site_name}"
|
222
|
-
Dir.exists?(app_root_path)
|
223
|
-
end
|
224
|
-
|
225
|
-
##
|
226
|
-
# Creates the +app_root_path+
|
227
|
-
def create_app_root
|
228
|
-
shell <<-RUN
|
229
|
-
mkdir #{app_root_path}
|
230
|
-
chown #{context.user_name}:#{context.user_name} #{app_root_path}
|
231
|
-
RUN
|
232
|
-
end
|
233
|
-
|
234
|
-
############################################################################################################
|
235
|
-
|
236
|
-
##
|
237
|
-
# Creates the app public structure at +app_public_path+
|
238
|
-
def create_app_public
|
239
|
-
shell <<-RUN
|
240
|
-
mkdir #{app_public_path}
|
241
|
-
touch #{app_public_path}/index.html
|
242
|
-
chown -R #{context.user_name}:#{context.user_name} #{app_public_path}
|
243
|
-
RUN
|
244
|
-
end
|
245
|
-
|
246
|
-
############################################################################################################
|
247
|
-
|
248
|
-
##
|
249
|
-
# Creates the app log structure at +app_log_path+
|
250
|
-
def create_app_logs
|
251
|
-
shell <<-RUN
|
252
|
-
mkdir #{app_log_path}
|
253
|
-
mkdir #{app_log_path}/apache2
|
254
|
-
chmod 750 #{app_log_path}/apache2
|
255
|
-
|
256
|
-
touch #{app_log_path}/apache2/access.log
|
257
|
-
touch #{app_log_path}/apache2/error.log
|
258
|
-
|
259
|
-
chmod 640 #{app_log_path}/apache2/*.log
|
260
|
-
chown -R #{context.user_name}:#{context.user_name} #{app_log_path}
|
261
|
-
chown root -R #{app_log_path}/apache2
|
262
|
-
RUN
|
263
|
-
end
|
264
|
-
|
265
|
-
############################################################################################################
|
266
|
-
|
267
|
-
##
|
268
|
-
# Determines if the apache2 vhost config file already exists
|
269
|
-
# @return [Boolean] +true+ if the file exists, otherwise +false+
|
270
|
-
def vhost_config_exists?
|
271
|
-
Dir.exists?(vhost_file_path)
|
272
|
-
end
|
273
|
-
|
274
|
-
##
|
275
|
-
# Creates an apache2 vhost config file from a template file to a +vhost_file_path+
|
276
|
-
def create_vhost_config
|
277
|
-
File.open(vhost_file_path, 'wt') do |file|
|
278
|
-
template_context = {
|
279
|
-
:site_name => context.site_name,
|
280
|
-
:site_aliases => context.site_aliases,
|
281
|
-
:app_public_path => app_public_path,
|
282
|
-
:app_log_path => app_log_path
|
283
|
-
}
|
284
|
-
|
285
|
-
config = context.evaluate_template(vhost_template_file_path, template_context)
|
286
|
-
file.write(config)
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
############################################################################################################
|
291
|
-
|
292
|
-
##
|
293
|
-
# Installs the apache2 vhost config file by enabling the site and restarting the web server
|
294
|
-
def install_vhost_config
|
295
|
-
shell <<-RUN
|
296
|
-
a2ensite #{vhost_file_name}
|
297
|
-
apache2ctl graceful
|
298
|
-
RUN
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
end
|
303
|
-
end
|
304
|
-
end
|