capistrano-offroad 0.0.1 → 0.1.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/README +334 -0
- data/lib/capistrano-offroad/modules/daemontools.rb +3 -3
- data/lib/capistrano-offroad/modules/django.rb +2 -2
- data/lib/capistrano-offroad/modules/monit.rb +1 -1
- data/lib/capistrano-offroad/modules/supervisord.rb +4 -4
- data/lib/capistrano-offroad/reset.rb +7 -1
- data/lib/capistrano-offroad/utils.rb +10 -8
- data/lib/capistrano-offroad/version.rb +8 -2
- data/lib/capistrano-offroad.rb +1 -0
- metadata +27 -8
data/README
CHANGED
@@ -0,0 +1,334 @@
|
|
1
|
+
Capistrano-offroad
|
2
|
+
==================
|
3
|
+
|
4
|
+
Author: Maciej Pasternacki <maciej@pasternacki.net>
|
5
|
+
Date: 2010-05-28 Fri
|
6
|
+
|
7
|
+
|
8
|
+
Capistrano-offroad is a support package for using Capistrano with
|
9
|
+
non-rails projects. It contains basic reset of Rails-specific tasks,
|
10
|
+
a handful of utility functions, and modules with recipes.
|
11
|
+
|
12
|
+
Capistrano-offroad is available on terms of BSD license. See LICENSE
|
13
|
+
file for details.
|
14
|
+
|
15
|
+
Table of Contents
|
16
|
+
=================
|
17
|
+
1 Installation
|
18
|
+
1.1 System-wide installation
|
19
|
+
1.2 Project-local installation
|
20
|
+
2 Usage
|
21
|
+
3 Features
|
22
|
+
3.1 Reset
|
23
|
+
3.2 Utilities
|
24
|
+
3.2.1 `:run' dependency type
|
25
|
+
3.2.2 `set_from_env_or_ask'
|
26
|
+
3.2.3 `offroad_modules'
|
27
|
+
3.3 Version
|
28
|
+
3.3.1 `require_version'
|
29
|
+
3.4 Modules
|
30
|
+
3.4.1 defaults
|
31
|
+
3.4.2 django
|
32
|
+
3.4.3 supervisord
|
33
|
+
3.4.4 daemontools
|
34
|
+
3.4.5 monit
|
35
|
+
|
36
|
+
|
37
|
+
1 Installation
|
38
|
+
~~~~~~~~~~~~~~~
|
39
|
+
|
40
|
+
1.1 System-wide installation
|
41
|
+
=============================
|
42
|
+
|
43
|
+
Capistrano-offroad is available as a Ruby Gem, so in most cases you
|
44
|
+
should simply call:
|
45
|
+
|
46
|
+
gem install capistrano-offroad
|
47
|
+
|
48
|
+
This requires fairly recent version of Ruby gems (1.3.6 or later),
|
49
|
+
so it's possible you'll need to upgrade rubygems itself:
|
50
|
+
|
51
|
+
gem update --system
|
52
|
+
|
53
|
+
You can also download source code directly from Github page
|
54
|
+
[http://github.com/mpasternacki/capistrano-offroad] and install it by
|
55
|
+
rebuilding the gem manually.
|
56
|
+
|
57
|
+
1.2 Project-local installation
|
58
|
+
===============================
|
59
|
+
|
60
|
+
You can download source code from Github and use it as a Git
|
61
|
+
submodule, or just drop it into your code base in whatever way
|
62
|
+
suits you. Then, you should add to you Capfile the magic line
|
63
|
+
adding capistrano-offroad's `lib/' subdirectory to the require
|
64
|
+
path.
|
65
|
+
|
66
|
+
$:.unshift File.join( File.dirname(__FILE__), 'relative/path/to/capistrano-offroad/lib' )
|
67
|
+
|
68
|
+
2 Usage
|
69
|
+
~~~~~~~~
|
70
|
+
|
71
|
+
When you've got capistrano-offroad on your require path, whether
|
72
|
+
globally from a gem, or by extending require path, you simply
|
73
|
+
`require' it.
|
74
|
+
|
75
|
+
require 'capistrano-offroad'
|
76
|
+
|
77
|
+
For some reason, this line should appear *after* the
|
78
|
+
`set :application' declaration.
|
79
|
+
|
80
|
+
After that, you either `require' desired modules yourself, or use
|
81
|
+
`offroad_modules' helper function.
|
82
|
+
|
83
|
+
require 'capistrano-offroad/modules/defaults'
|
84
|
+
require 'capistrano-offroad/modules/django'
|
85
|
+
|
86
|
+
offroad_modules 'defaults', 'django'
|
87
|
+
|
88
|
+
3 Features
|
89
|
+
~~~~~~~~~~~
|
90
|
+
|
91
|
+
3.1 Reset
|
92
|
+
==========
|
93
|
+
By default, capistrano-offroad empties Rails-specific tasks:
|
94
|
+
`deploy:migrate', `deploy:start', `deploy:stop', `deploy:restart'.
|
95
|
+
|
96
|
+
The `:deploy:finalize_update' task is cut down to only run `chgrp'
|
97
|
+
and `chmod' when `:group_writable' setting is true. `chgrp'
|
98
|
+
behaviour can be modified by setting `:group_writable' to:
|
99
|
+
- `:no_chgrp' - don't run `chgrp' command
|
100
|
+
- `:sudo_chgrp' - use `sudo' to make sure `chgrp' has proper permissions
|
101
|
+
|
102
|
+
The setting `:shared_children' is set to an empty list. This gives
|
103
|
+
comfortable enough blank slate for working on non-rails projects,
|
104
|
+
without redefining all the workflow that is already present in
|
105
|
+
Capistrano.
|
106
|
+
|
107
|
+
3.2 Utilities
|
108
|
+
==============
|
109
|
+
By default, two extensions to Capistrano are defined.
|
110
|
+
|
111
|
+
3.2.1 `:run' dependency type
|
112
|
+
-----------------------------
|
113
|
+
Usage:
|
114
|
+
depend :remote, :run, "full shell command"=
|
115
|
+
|
116
|
+
When running `cap deploy:check', run the supplied shell command.
|
117
|
+
If its exit status is non-zero, the dependency has failed.
|
118
|
+
|
119
|
+
3.2.2 `set_from_env_or_ask'
|
120
|
+
----------------------------
|
121
|
+
Usage:
|
122
|
+
set_from_env_or_ask :setting_name, "Query prompt: "
|
123
|
+
|
124
|
+
If there is `SETTING_NAME' (uppercased first argument) in
|
125
|
+
environment (i.e. supplied from command line), set `:setting_name'
|
126
|
+
to value of this environment variable.
|
127
|
+
|
128
|
+
If the environment variable is not defined, ask user interactively
|
129
|
+
for a value, using query prompt supplied as a second argument.
|
130
|
+
|
131
|
+
3.2.3 `offroad_modules'
|
132
|
+
------------------------
|
133
|
+
Loads capistrano-offroad modules named as arguments. Convenience
|
134
|
+
function, so that user doesn't have to type `require' lines by himself.
|
135
|
+
|
136
|
+
3.3 Version
|
137
|
+
============
|
138
|
+
Three constants defining version of capistrano-offroad are defined:
|
139
|
+
|
140
|
+
CapistranoOffroad::VERSION::MAJOR
|
141
|
+
CapistranoOffroad::VERSION::MINOR
|
142
|
+
CapistranoOffroad::VERSION::TINY
|
143
|
+
|
144
|
+
Convenience string constant with full version number, separated
|
145
|
+
with dots, is provided:
|
146
|
+
|
147
|
+
CapistranoOffroad::VERSION::STRING
|
148
|
+
|
149
|
+
3.3.1 `require_version'
|
150
|
+
------------------------
|
151
|
+
A helper function, which can be used in Capfile to enforce a minimum
|
152
|
+
version of capistrano-offroad:
|
153
|
+
|
154
|
+
CapistranoOffroad::VERSION.require_version(major, minor=0, tiny=0)
|
155
|
+
|
156
|
+
3.4 Modules
|
157
|
+
============
|
158
|
+
Modules are packs of recipes and helpers for dealing with specific
|
159
|
+
software.
|
160
|
+
|
161
|
+
To load a module, either use the `require' function:
|
162
|
+
|
163
|
+
require 'capistrano-offroad/modules/module-name'
|
164
|
+
|
165
|
+
or use supplied `offroad_modules' helper function:
|
166
|
+
|
167
|
+
offroad_modules "foo", "bar", "baz", ...
|
168
|
+
|
169
|
+
When using a module, it is advised to at least browse through its
|
170
|
+
source to know what to expect.
|
171
|
+
|
172
|
+
Following modules are defined:
|
173
|
+
|
174
|
+
3.4.1 defaults
|
175
|
+
---------------
|
176
|
+
|
177
|
+
Contains my personal defaults for settings. For me it's part of
|
178
|
+
reset, but I moved it out to a separate module, because other
|
179
|
+
users may have different opinions. The settings are:
|
180
|
+
|
181
|
+
set :scm, :git
|
182
|
+
set :ssh_options, { :forward_agent => true }
|
183
|
+
set :use_sudo, false
|
184
|
+
set :deploy_to, "/srv/#{application}"
|
185
|
+
|
186
|
+
3.4.2 django
|
187
|
+
-------------
|
188
|
+
Settings, utilities and recipes for deploying [Django] projects.
|
189
|
+
|
190
|
+
[Django]: http://djangoproject.org/
|
191
|
+
|
192
|
+
* Settings
|
193
|
+
Following settings are defined.
|
194
|
+
+ :python
|
195
|
+
Python interpreter command. Defaults to "python".
|
196
|
+
|
197
|
+
Module also adds a dependency on this command to `deploy:check'.
|
198
|
+
+ :django_project_subdirectory
|
199
|
+
Directory in your repository that contains Django project (one
|
200
|
+
with the the `manage.py' file). Defaults to "project".
|
201
|
+
+ :django_use_south
|
202
|
+
Assume [South] is used for database migrations. Defaults to true.
|
203
|
+
|
204
|
+
[South]: http://south.aeracode.org/
|
205
|
+
|
206
|
+
* Functions and utilities
|
207
|
+
django_manage cmd, options={}
|
208
|
+
Runs the `manage.py' command `cmd'. Optional keyword argument
|
209
|
+
`:path' provides path to a Capistrano release where the command
|
210
|
+
should be run. It defaults to `:latest_release'.
|
211
|
+
|
212
|
+
`:python_module' dependency type is defined to name Python
|
213
|
+
modules in `deploy:check' dependencies:
|
214
|
+
|
215
|
+
depend :remote, :python_module, "MySQLdb"
|
216
|
+
* Tasks
|
217
|
+
+ django:manage
|
218
|
+
Run custom Django management command in latest release.
|
219
|
+
|
220
|
+
Pass the management command and arguments in COMMAND="..."
|
221
|
+
variable. If COMMAND variable is not provided, Capistrano will
|
222
|
+
ask for a command.
|
223
|
+
|
224
|
+
+ deploy:migrate
|
225
|
+
Runs `manage.py syncdb' in the latest release. If
|
226
|
+
`:django_use_south' is true, it `--migrate' switch is used.
|
227
|
+
- TODO make it run in specified release, as vanilla Rails `deploy:migrate'
|
228
|
+
+ TODO separate `python' module
|
229
|
+
Stuff that is not Django-specific should be moved to a separate
|
230
|
+
`python' module that could be used with Python-related
|
231
|
+
non-Django projects. The `python' module would be automatically
|
232
|
+
loaded by `django' module.
|
233
|
+
|
234
|
+
|
235
|
+
3.4.3 supervisord
|
236
|
+
------------------
|
237
|
+
Control processes with [the supervisor daemon].
|
238
|
+
|
239
|
+
|
240
|
+
[the supervisor daemon]: http://supervisord.org/
|
241
|
+
|
242
|
+
* Settings
|
243
|
+
set :supervisord_path, "" # directory where supervisord binaries reside
|
244
|
+
set :supervisord_command, "supervisord"
|
245
|
+
set :supervisorctl_command, "supervisorctl"
|
246
|
+
set :supervisord_conf, "supervisord_conf"
|
247
|
+
set :supervisord_pidfile, "supervisord.pid"
|
248
|
+
set :supervisord_start_group, nil # process group to start on deploy:start - nil means all processes
|
249
|
+
set :supervisord_stop_group, nil # process group to stop on deploy:stop - nil means all processes
|
250
|
+
|
251
|
+
* Utilities
|
252
|
+
supervisorctl(cmd, options={})
|
253
|
+
Run a `supervisorctl' command specified as first argument. If
|
254
|
+
optional keyword argument `:try_start' is true (the default, you
|
255
|
+
may specify as false), start `supervisord' if not already
|
256
|
+
running.
|
257
|
+
|
258
|
+
* Tasks
|
259
|
+
+ Standard tasks
|
260
|
+
Stock Capistrano tasks `deploy:start', `deploy:stop',
|
261
|
+
`deploy:restart' are defined. They optionally accept `GROUP' or
|
262
|
+
`PROGRAM' environment variables, which can specify a single,
|
263
|
+
specific process group or a single process name to start, stop
|
264
|
+
or restart.
|
265
|
+
+ deploy:status
|
266
|
+
Runs `supervisorctl status'.
|
267
|
+
+ deploy:processes
|
268
|
+
Runs `pstree' with root in supervisord if supervisord is
|
269
|
+
running.
|
270
|
+
+ deploy:reload_supervisord
|
271
|
+
Reloads supervisor daemon's config. Starts it if not started.
|
272
|
+
+ deploy:supervisorctl
|
273
|
+
Runs supplied supervisorctl command. Command should be provided
|
274
|
+
in COMMAND variable; if no variable is provided,
|
275
|
+
capistrano-offroad will ask for the command.
|
276
|
+
|
277
|
+
|
278
|
+
3.4.4 daemontools
|
279
|
+
------------------
|
280
|
+
Recipes and settings for controlling processes with Dan
|
281
|
+
Bernstein's [daemontools]. It expects that `run' script lives in
|
282
|
+
top level of your repository.
|
283
|
+
|
284
|
+
*WARNING*: this is legacy code, used only in old projects and not
|
285
|
+
very well supported.
|
286
|
+
|
287
|
+
Also, this is probably not the best way to deal with the problem -
|
288
|
+
it would be easier to manage, start and restart with the `run'
|
289
|
+
script in the `:deploy_to' root, which would `cd' to `current' and
|
290
|
+
`exec ./run'. If anybody wants to write a patch to support that,
|
291
|
+
it would be great :)
|
292
|
+
|
293
|
+
[daemontools]: http://cr.yp.to/daemontools.html
|
294
|
+
|
295
|
+
* Settings
|
296
|
+
set :svscan_root, "/service"
|
297
|
+
set :supervise_name, "#{application}"
|
298
|
+
* Utilities
|
299
|
+
`svc' function runs `svc' with supplied arguments for the
|
300
|
+
supervised directory.
|
301
|
+
* Tasks
|
302
|
+
- `daemontools:create_symlink' - creates a symlink in `:svscan_root'
|
303
|
+
- `daemontools:remove_symlink' - removes symlink from
|
304
|
+
`:svscan_root' and stops the app
|
305
|
+
- `daemontools:status' - displays `svstat' output for current release
|
306
|
+
- `daemontools:relstatus' - displays `svstat' output for all
|
307
|
+
releases (use it after restart to make sure that previous
|
308
|
+
release has actually stopped)
|
309
|
+
- `deploy:start', `deploy:stop', `deploy:restart' - standard
|
310
|
+
Capistrano tasks
|
311
|
+
- `deploy:symlink' internal target is redefined; `on_rollback'
|
312
|
+
handler is currently broken
|
313
|
+
|
314
|
+
|
315
|
+
3.4.5 monit
|
316
|
+
------------
|
317
|
+
Module to run processes with monit.
|
318
|
+
*This is legacy, unsupported, unused code*. Use it (and monit) on
|
319
|
+
your own risk. I'm leaving it in only because the code is already
|
320
|
+
written and it would be waste to throw it away only because monit
|
321
|
+
sucks.
|
322
|
+
* Settings
|
323
|
+
set :monit_group, nil # process group to start/stop/reload
|
324
|
+
set :monit_command, "monit"
|
325
|
+
|
326
|
+
* Tasks
|
327
|
+
- `deploy:start', `deploy:stop', `deploy:restart' - standard
|
328
|
+
Capistrano tasks. They accept an extra optional variable
|
329
|
+
PROCESS which, if given, specifies a single process to start,
|
330
|
+
stop or restart. If PROCESS is not given, all processes (of
|
331
|
+
`:monit_group' group, if configured) are started/stopped/restarted.
|
332
|
+
- `deploy:status' - displays `monit summary' output
|
333
|
+
- `deploy:status:full' - displays `monit status' output
|
334
|
+
- `deploy:reload_monit' - reloads monit configuration
|
@@ -16,13 +16,13 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
desc "[internal] Remove symlink from svscan directory"
|
19
|
-
task :
|
19
|
+
task :do_remove_symlink do
|
20
20
|
sudo "rm -v #{svscan_root}/#{supervise_name}"
|
21
21
|
end
|
22
22
|
|
23
23
|
desc "Remove symlink from svscan directory and stop supervise"
|
24
|
-
task :
|
25
|
-
|
24
|
+
task :remove_symlink do
|
25
|
+
do_remove_symlink
|
26
26
|
sudo "svc -x -t #{current_path}"
|
27
27
|
end
|
28
28
|
|
@@ -33,8 +33,8 @@ EOF
|
|
33
33
|
desc "Run manage.py syncdb in latest release."
|
34
34
|
task :migrate, :roles => :db, :only => { :primary => true } do
|
35
35
|
# FIXME: path, see default railsy deploy:migrate
|
36
|
-
|
37
|
-
django_manage "
|
36
|
+
m = if fetch(:django_use_south, false) then "--migrate" else "" end
|
37
|
+
django_manage "syncdb --noinput #{m}"
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'capistrano'
|
2
2
|
|
3
3
|
Capistrano::Configuration.instance(:must_exist).load do
|
4
|
-
set :supervisord_path, ""
|
4
|
+
set :supervisord_path, "" # directory where supervisord binaries reside
|
5
5
|
set :supervisord_command, "supervisord"
|
6
6
|
set :supervisorctl_command, "supervisorctl"
|
7
7
|
set :supervisord_conf, "supervisord_conf"
|
8
8
|
set :supervisord_pidfile, "supervisord.pid"
|
9
|
-
set :supervisord_start_group, nil
|
10
|
-
set :supervisord_stop_group, nil
|
9
|
+
set :supervisord_start_group, nil # process group to start on deploy:start - nil means all processes
|
10
|
+
set :supervisord_stop_group, nil # process group to stop on deploy:stop - nil means all processes
|
11
11
|
|
12
12
|
namespace :deploy do
|
13
13
|
def supervisord_pidfile_path ; "#{shared_path}/#{supervisord_pidfile}" end
|
@@ -86,7 +86,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
86
86
|
supervisorctl "reload"
|
87
87
|
end
|
88
88
|
|
89
|
-
task :
|
89
|
+
task :supervisorctl do
|
90
90
|
set_from_env_or_ask :command, "supervisorctl command: "
|
91
91
|
supervisorctl "#{command}", :try_start => false
|
92
92
|
end
|
@@ -8,7 +8,13 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
8
8
|
namespace :deploy do
|
9
9
|
task :finalize_update, :except => { :no_release => true } do
|
10
10
|
if fetch(:group_writable, true)
|
11
|
-
|
11
|
+
if :no_chgrp == fetch(:group_writable, true)
|
12
|
+
# skip step
|
13
|
+
elsif :sudo_chgrp == fetch(:group_writable, true)
|
14
|
+
sudo "chgrp -R #{deploy_group} #{latest_release}"
|
15
|
+
else
|
16
|
+
run "chgrp -R #{deploy_group} #{latest_release}"
|
17
|
+
end
|
12
18
|
run "chmod -R g+w #{latest_release}"
|
13
19
|
end
|
14
20
|
end
|
@@ -12,13 +12,15 @@ class Capistrano::Deploy::RemoteDependency
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
class Capistrano::Configuration
|
16
|
+
# set_from_env_or_ask :variable, "Please enter variable name: "
|
17
|
+
# If there is VARIABLE in enviroment, set :variable to it, otherwise
|
18
|
+
# ask user for a value
|
19
|
+
def set_from_env_or_ask(sym, question)
|
20
|
+
if ENV.has_key? sym.to_s.upcase then
|
21
|
+
set sym, ENV[sym.to_s.upcase]
|
22
|
+
else
|
23
|
+
set sym do Capistrano::CLI.ui.ask question end
|
24
|
+
end
|
23
25
|
end
|
24
26
|
end
|
@@ -1,8 +1,14 @@
|
|
1
1
|
module CapistranoOffroad
|
2
2
|
module VERSION
|
3
3
|
MAJOR = 0
|
4
|
-
MINOR =
|
5
|
-
TINY =
|
4
|
+
MINOR = 1
|
5
|
+
TINY = 0
|
6
6
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
7
|
+
|
8
|
+
def VERSION.require_version(major, minor=0, tiny=0)
|
9
|
+
unless ([MAJOR, MINOR, TINY] <=> [major, minor, tiny]) >= 0
|
10
|
+
raise Capistrano::Error, "capistrano-offroad version #{MAJOR}.#{MINOR}.#{TINY} is below required #{major}.#{minor}.#{tiny}"
|
11
|
+
end
|
12
|
+
end
|
7
13
|
end
|
8
14
|
end
|
data/lib/capistrano-offroad.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capistrano-offroad
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Maciej Pasternacki
|
@@ -15,11 +15,30 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-05-
|
18
|
+
date: 2010-05-28 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
|
-
dependencies:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: capistrano
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 11
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 5
|
33
|
+
- 8
|
34
|
+
version: 2.5.8
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: |
|
38
|
+
Capistrano-offroad is a support package for using Capistrano with
|
39
|
+
non-rails projects. It contains basic reset of Rails-specific tasks,
|
40
|
+
a handful of utility functions, and modules with recipes.
|
21
41
|
|
22
|
-
description: Capistrano add-ons and recipes for non-rails projects
|
23
42
|
email: maciej@pasternacki.net
|
24
43
|
executables: []
|
25
44
|
|
@@ -41,8 +60,8 @@ files:
|
|
41
60
|
- LICENSE
|
42
61
|
has_rdoc: true
|
43
62
|
homepage: http://github.com/mpasternacki/capistrano-offroad
|
44
|
-
licenses:
|
45
|
-
|
63
|
+
licenses:
|
64
|
+
- BSD
|
46
65
|
post_install_message:
|
47
66
|
rdoc_options: []
|
48
67
|
|