kajiki 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 45deee46f2c58f9acf4080fb0ee89c665065179b
4
+ data.tar.gz: 5a917d34214777658247ccffeb2ad05188d1234a
5
+ SHA512:
6
+ metadata.gz: 2a6501c719e6c7301ffe71e62f6859af07d4d2c04636528158ddd856b780ef1f0d49ac8022484ea07c184eced25c1270a53c726bee7797fd464b38f76444b30a
7
+ data.tar.gz: 327d5832027cfe1066d98b08f34b7cd2987fd4203227867107ec9fcebd3e99ce324cb524ef220fa5f90c83ac0cee1bea11cf7a8762289984a8bd82510f946e0f
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Ken J.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,69 @@
1
+ # Kajiki
2
+
3
+ A simple gem to build daemons.
4
+
5
+ It provides basic functions to start and stop a daemon process. It also parses command-line options.
6
+
7
+ ## Requirements
8
+
9
+ - Ruby 2.0.0 <=
10
+
11
+ Kajiki has no gem dependencies. It uses [Trollop](https://rubygems.org/gems/trollop), but it's embedded.
12
+
13
+ ## Getting Started
14
+
15
+ ### Install
16
+
17
+ ```
18
+ $ gem install kajiki
19
+ ```
20
+
21
+ ### Code
22
+
23
+ ```ruby
24
+ require 'kajiki'
25
+
26
+ opts = Kajiki.preset_options(:minimal)
27
+ Kajiki.run(opts) do |command|
28
+ case command
29
+ when 'start'
30
+ while true
31
+ puts 'Are we there yet?'
32
+ sleep(5)
33
+ end
34
+ when 'stop'
35
+ puts 'Arrived!'
36
+ end
37
+ end
38
+ ```
39
+
40
+ ### Use
41
+
42
+ To see help:
43
+
44
+ ```
45
+ $ myapp -h
46
+ Usage: myapp [options] {start|stop}
47
+ -d, --daemonize Run in the background
48
+ -p, --pid=<s> Store PID to file
49
+ -h, --help Show this message
50
+ ```
51
+
52
+ To start your app as a daemon:
53
+
54
+ ```
55
+ $ myapp -d -p ~/myapp.pid start
56
+ ```
57
+
58
+ To stop your app running as a daemon:
59
+
60
+ ```
61
+ $ myapp -p ~/myapp.pid stop
62
+ ```
63
+
64
+ ### More
65
+
66
+ There are two ways to use Kajiki.
67
+
68
+ 1. With preset command-line option parsing and auto daemonizing
69
+ 2. Custom option parsing and semi-auto daemonizing
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "kajiki"
3
+ s.version = "1.0.0"
4
+ s.authors = ["Ken J."]
5
+ s.email = ["kenjij@gmail.com"]
6
+ s.description = %q{A simple gem to build daemons}
7
+ s.summary = %q{A simple Ruby gem to ease building daemons.}
8
+ s.homepage = "https://github.com/kenjij/kajiki"
9
+ s.license = "MIT"
10
+
11
+ s.files = `git ls-files`.split($/)
12
+ s.require_paths = ["lib"]
13
+ end
@@ -0,0 +1,11 @@
1
+ require 'kajiki/trollop'
2
+ require 'kajiki/presets'
3
+ require 'kajiki/runner'
4
+
5
+ module Kajiki
6
+
7
+ def self.run(opts, &block)
8
+ Runner.run(opts, &block)
9
+ end
10
+
11
+ end
@@ -0,0 +1,88 @@
1
+ module Kajiki
2
+
3
+ module Handler
4
+
5
+ # Check if process exists then fail, otherwise clean up.
6
+ # @return [Boolean] `false` if no PID file exists, `true` if it cleaned up.
7
+ def check_existing_pid
8
+ return false unless pid_file_exists?
9
+ pid = read_pid
10
+ fail 'Existing process found.' if pid > 0 && pid_exists?(pid)
11
+ delete_pid
12
+ end
13
+
14
+ # Check if process exists.
15
+ # @param pid [Fixnum]
16
+ # @return [Boolean]
17
+ def pid_exists?(pid)
18
+ Process.kill(0, pid)
19
+ return true
20
+ rescue Errno::ESRCH
21
+ return false
22
+ end
23
+
24
+ # Check if PID file exists.
25
+ # @return [Boolean]
26
+ def pid_file_exists?
27
+ return false unless @opts[:pid_given]
28
+ File.exists?(@opts[:pid])
29
+ end
30
+
31
+ # Write PID to file.
32
+ def write_pid
33
+ IO.write(@opts[:pid], Process.pid) if @opts[:pid_given]
34
+ end
35
+
36
+ # Read PID from file.
37
+ # @return [Fixnum, nil] PID; if `0`, it should be ignored.
38
+ def read_pid
39
+ IO.read(@opts[:pid]).to_i if @opts[:pid_given]
40
+ end
41
+
42
+ # Delete PID file if it exists.
43
+ # @return [Boolean] `false` if nothing done, `true` if file was deleted.
44
+ def delete_pid
45
+ if @opts[:pid_given] && File.exists?(@opts[:pid])
46
+ File.delete(@opts[:pid])
47
+ return true
48
+ end
49
+ return false
50
+ end
51
+
52
+ # Change process UID and GID.
53
+ def change_privileges
54
+ Process.egid = @opts[:group] if @opts[:group_given]
55
+ Process.euid = @opts[:user] if @opts[:user_given]
56
+ end
57
+
58
+ # Redirect outputs.
59
+ def redirect_outputs
60
+ if @opts[:error_given]
61
+ $stderr.reopen(@opts[:error], 'a')
62
+ $stderr.sync = true
63
+ end
64
+ if @opts[:log_given]
65
+ $stdout.reopen(@opts[:log], 'a')
66
+ $stdout.sync = true
67
+ end
68
+ end
69
+
70
+ # Trap common signals as default.
71
+ def trap_default_signals
72
+ Signal.trap('INT') do
73
+ puts 'Interrupted. Terminating process...'
74
+ exit
75
+ end
76
+ Signal.trap('HUP') do
77
+ puts 'SIGHUP - Terminating process...'
78
+ exit
79
+ end
80
+ Signal.trap('TERM') do
81
+ puts 'SIGTERM - Terminating process...'
82
+ exit
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,57 @@
1
+ module Kajiki
2
+
3
+ # Available commands.
4
+ SUB_COMMANDS = %w(start stop)
5
+
6
+ # Description strings for help display.
7
+ OPT_DESCS = {
8
+ banner: "Usage: #{$0} [options] {#{SUB_COMMANDS.join('|')}}",
9
+ address: 'Bind to address',
10
+ daemonize: 'Run in the background',
11
+ error: 'Output error to file',
12
+ group: 'Group to run as',
13
+ log: 'Log output to file',
14
+ pid: 'Store PID to file',
15
+ port: 'Use port',
16
+ user: 'User to run as'
17
+ }
18
+
19
+ # Preset options for command-line parsing.
20
+ # @param [Symbol] presets are: `:minimal`, `:simple`, or `:server`.
21
+ def self.preset_options(preset)
22
+ case preset
23
+ when :minimal
24
+ Trollop.options do
25
+ banner OPT_DESCS[:banner]
26
+ opt :daemonize, OPT_DESCS[:daemonize]
27
+ opt :pid, OPT_DESCS[:pid], type: :string
28
+ end
29
+ when :simple
30
+ Trollop.options do
31
+ banner OPT_DESCS[:banner]
32
+ opt :daemonize, OPT_DESCS[:daemonize]
33
+ opt :error, OPT_DESCS[:error], type: :string
34
+ opt :group, OPT_DESCS[:group], type: :string
35
+ opt :log, OPT_DESCS[:log], type: :string
36
+ opt :pid, OPT_DESCS[:pid], type: :string
37
+ opt :user, OPT_DESCS[:user], type: :string
38
+ depends(:user, :group)
39
+ end
40
+ when :server
41
+ Trollop.options do
42
+ opt :address, OPT_DESCS[:address], default: '0.0.0.0'
43
+ opt :daemonize, OPT_DESCS[:daemonize]
44
+ opt :error, OPT_DESCS[:error], type: :string
45
+ opt :group, OPT_DESCS[:group], type: :string
46
+ opt :log, OPT_DESCS[:log], type: :string
47
+ opt :pid, OPT_DESCS[:pid], short: 'P', type: :string
48
+ opt :port, OPT_DESCS[:port], default: 4567
49
+ opt :user, OPT_DESCS[:user], type: :string
50
+ depends(:user, :group)
51
+ end
52
+ else
53
+ fail 'Invalid preset option.'
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,77 @@
1
+ require 'kajiki/handler'
2
+
3
+ module Kajiki
4
+
5
+ class Runner
6
+
7
+ include Handler
8
+
9
+ # A wrapper to execute all commands.
10
+ # @param opts [Hash] the command-line options.
11
+ # @param block [Block] the command (String) will be passed.
12
+ def self.run(opts, &block)
13
+ opts[:auto_default_actions] = true
14
+ runner = new(opts)
15
+ runner.execute_command(&block)
16
+ end
17
+
18
+ # @param opts [Hash] the command-line options.
19
+ def initialize(opts)
20
+ @opts = opts
21
+ end
22
+
23
+ # Execute the command with the given block; usually called by ::run.
24
+ def execute_command(cmd = ARGV, &block)
25
+ cmd = validate_command(cmd)
26
+ validate_options
27
+ send(cmd, &block)
28
+ end
29
+
30
+ # Validate the given command.
31
+ # @param cmd [Array] only one command should be given.
32
+ # @return [String] extracts out of Array
33
+ def validate_command(cmd = ARGV)
34
+ fail 'Specify one action.' unless cmd.count == 1
35
+ cmd = cmd.shift
36
+ fail 'Invalid action.' unless SUB_COMMANDS.include?(cmd)
37
+ cmd
38
+ end
39
+
40
+ # Validate the options; otherwise fails.
41
+ def validate_options
42
+ if @opts[:daemonize]
43
+ fail 'Must specify PID file.' unless @opts[:pid_given]
44
+ end
45
+ @opts[:pid] = File.expand_path(@opts[:pid]) if @opts[:pid_given]
46
+ @opts[:log] = File.expand_path(@opts[:log]) if @opts[:log_given]
47
+ @opts[:error] = File.expand_path(@opts[:error]) if @opts[:error_given]
48
+ end
49
+
50
+ # Start the process with the given block.
51
+ # @param [Block]
52
+ def start(&block)
53
+ fail 'No start block given.' if block.nil?
54
+ check_existing_pid
55
+ puts "Starting process..."
56
+ Process.daemon if @opts[:daemonize]
57
+ change_privileges if opts[:auto_default_actions]
58
+ redirect_outputs if opts[:auto_default_actions]
59
+ write_pid
60
+ trap_default_signals if opts[:auto_default_actions]
61
+ block.call('start')
62
+ end
63
+
64
+ # Stop the process.
65
+ # @param [Block] will execute prior to shutdown, if given.
66
+ def stop(&block)
67
+ block.call('stop') unless block.nil?
68
+ pid = read_pid
69
+ fail 'No valid PID file.' unless pid && pid > 0
70
+ Process.kill('TERM', pid)
71
+ delete_pid
72
+ puts 'Process terminated.'
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,846 @@
1
+ # lib/trollop.rb -- trollop command-line processing library
2
+ # Copyright (c) 2008-2014 William Morgan.
3
+ # Copyright (c) 2014 Red Hat, Inc.
4
+ # trollop is licensed under the same terms as Ruby.
5
+
6
+ require 'date'
7
+
8
+ module Trollop
9
+ VERSION = "2.1.1"
10
+
11
+ ## Thrown by Parser in the event of a commandline error. Not needed if
12
+ ## you're using the Trollop::options entry.
13
+ class CommandlineError < StandardError; end
14
+
15
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
16
+ ## automatically by Trollop#options.
17
+ class HelpNeeded < StandardError; end
18
+
19
+ ## Thrown by Parser if the user passes in '-v' or '--version'. Handled
20
+ ## automatically by Trollop#options.
21
+ class VersionNeeded < StandardError; end
22
+
23
+ ## Regex for floating point numbers
24
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
25
+
26
+ ## Regex for parameters
27
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
28
+
29
+ ## The commandline parser. In typical usage, the methods in this class
30
+ ## will be handled internally by Trollop::options. In this case, only the
31
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
32
+ ## typically be called.
33
+ ##
34
+ ## If you want to instantiate this class yourself (for more complicated
35
+ ## argument-parsing logic), call #parse to actually produce the output hash,
36
+ ## and consider calling it from within
37
+ ## Trollop::with_standard_exception_handling.
38
+ class Parser
39
+
40
+ ## The set of values that indicate a flag option when passed as the
41
+ ## +:type+ parameter of #opt.
42
+ FLAG_TYPES = [:flag, :bool, :boolean]
43
+
44
+ ## The set of values that indicate a single-parameter (normal) option when
45
+ ## passed as the +:type+ parameter of #opt.
46
+ ##
47
+ ## A value of +io+ corresponds to a readable IO resource, including
48
+ ## a filename, URI, or the strings 'stdin' or '-'.
49
+ SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date]
50
+
51
+ ## The set of values that indicate a multiple-parameter option (i.e., that
52
+ ## takes multiple space-separated values on the commandline) when passed as
53
+ ## the +:type+ parameter of #opt.
54
+ MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates]
55
+
56
+ ## The complete set of legal values for the +:type+ parameter of #opt.
57
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
58
+
59
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
60
+
61
+ ## The values from the commandline that were not interpreted by #parse.
62
+ attr_reader :leftovers
63
+
64
+ ## The complete configuration hashes for each option. (Mainly useful
65
+ ## for testing.)
66
+ attr_reader :specs
67
+
68
+ ## A flag that determines whether or not to raise an error if the parser is passed one or more
69
+ ## options that were not registered ahead of time. If 'true', then the parser will simply
70
+ ## ignore options that it does not recognize.
71
+ attr_accessor :ignore_invalid_options
72
+
73
+ ## Initializes the parser, and instance-evaluates any block given.
74
+ def initialize *a, &b
75
+ @version = nil
76
+ @leftovers = []
77
+ @specs = {}
78
+ @long = {}
79
+ @short = {}
80
+ @order = []
81
+ @constraints = []
82
+ @stop_words = []
83
+ @stop_on_unknown = false
84
+
85
+ #instance_eval(&b) if b # can't take arguments
86
+ cloaker(&b).bind(self).call(*a) if b
87
+ end
88
+
89
+ ## Define an option. +name+ is the option name, a unique identifier
90
+ ## for the option that you will use internally, which should be a
91
+ ## symbol or a string. +desc+ is a string description which will be
92
+ ## displayed in help messages.
93
+ ##
94
+ ## Takes the following optional arguments:
95
+ ##
96
+ ## [+:long+] Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the +name+ option into a string, and replacing any _'s by -'s.
97
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+.
98
+ ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_ARG_TYPES+ constant. If unset, the default argument type is +:flag+, meaning that the argument does not take a parameter. The specification of +:type+ is not necessary if a +:default+ is given.
99
+ ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a +:type+ is not necessary if a +:default+ is given. (But see below for an important caveat when +:multi+: is specified too.) If the argument is a flag, and the default is set to +true+, then if it is specified on the the commandline the value will be +false+.
100
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
101
+ ## [+:multi+] If set to +true+, allows multiple occurrences of the option on the commandline. Otherwise, only a single instance of the option is allowed. (Note that this is different from taking multiple parameters. See below.)
102
+ ##
103
+ ## Note that there are two types of argument multiplicity: an argument
104
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
105
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
106
+ ##
107
+ ## Arguments that take multiple values should have a +:type+ parameter
108
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
109
+ ## value of an array of the correct type (e.g. [String]). The
110
+ ## value of this argument will be an array of the parameters on the
111
+ ## commandline.
112
+ ##
113
+ ## Arguments that can occur multiple times should be marked with
114
+ ## +:multi+ => +true+. The value of this argument will also be an array.
115
+ ## In contrast with regular non-multi options, if not specified on
116
+ ## the commandline, the default value will be [], not nil.
117
+ ##
118
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
119
+ ## +:multi+ => +true+), in which case the value of the argument will be
120
+ ## an array of arrays.
121
+ ##
122
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
123
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
124
+ ## is a multi-value argument as well as a multi-occurrence argument.
125
+ ## In thise case, Trollop assumes that it's not a multi-value argument.
126
+ ## If you want a multi-value, multi-occurrence argument with a default
127
+ ## value, you must specify +:type+ as well.
128
+
129
+ def opt name, desc="", opts={}, &b
130
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
131
+
132
+ ## fill in :type
133
+ opts[:type] = # normalize
134
+ case opts[:type]
135
+ when :boolean, :bool; :flag
136
+ when :integer; :int
137
+ when :integers; :ints
138
+ when :double; :float
139
+ when :doubles; :floats
140
+ when Class
141
+ case opts[:type].name
142
+ when 'TrueClass', 'FalseClass'; :flag
143
+ when 'String'; :string
144
+ when 'Integer'; :int
145
+ when 'Float'; :float
146
+ when 'IO'; :io
147
+ when 'Date'; :date
148
+ else
149
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
150
+ end
151
+ when nil; nil
152
+ else
153
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
154
+ opts[:type]
155
+ end
156
+
157
+ ## for options with :multi => true, an array default doesn't imply
158
+ ## a multi-valued argument. for that you have to specify a :type
159
+ ## as well. (this is how we disambiguate an ambiguous situation;
160
+ ## see the docs for Parser#opt for details.)
161
+ disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type]
162
+ opts[:default].first
163
+ else
164
+ opts[:default]
165
+ end
166
+
167
+ type_from_default =
168
+ case disambiguated_default
169
+ when Integer; :int
170
+ when Numeric; :float
171
+ when TrueClass, FalseClass; :flag
172
+ when String; :string
173
+ when IO; :io
174
+ when Date; :date
175
+ when Array
176
+ if opts[:default].empty?
177
+ if opts[:type]
178
+ raise ArgumentError, "multiple argument type must be plural" unless MULTI_ARG_TYPES.include?(opts[:type])
179
+ nil
180
+ else
181
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
182
+ end
183
+ else
184
+ case opts[:default][0] # the first element determines the types
185
+ when Integer; :ints
186
+ when Numeric; :floats
187
+ when String; :strings
188
+ when IO; :ios
189
+ when Date; :dates
190
+ else
191
+ raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
192
+ end
193
+ end
194
+ when nil; nil
195
+ else
196
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
197
+ end
198
+
199
+ raise ArgumentError, ":type specification and default type don't match (default type is #{type_from_default})" if opts[:type] && type_from_default && opts[:type] != type_from_default
200
+
201
+ opts[:type] = opts[:type] || type_from_default || :flag
202
+
203
+ ## fill in :long
204
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
205
+ opts[:long] = case opts[:long]
206
+ when /^--([^-].*)$/; $1
207
+ when /^[^-]/; opts[:long]
208
+ else; raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
209
+ end
210
+ raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
211
+
212
+ ## fill in :short
213
+ opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none
214
+ opts[:short] = case opts[:short]
215
+ when /^-(.)$/; $1
216
+ when nil, :none, /^.$/; opts[:short]
217
+ else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
218
+ end
219
+
220
+ if opts[:short]
221
+ raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
222
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
223
+ end
224
+
225
+ ## fill in :default for flags
226
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
227
+
228
+ ## autobox :default for :multi (multi-occurrence) arguments
229
+ opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array)
230
+
231
+ ## fill in :multi
232
+ opts[:multi] ||= false
233
+ opts[:callback] ||= b if block_given?
234
+ opts[:desc] ||= desc
235
+ @long[opts[:long]] = name
236
+ @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
237
+ @specs[name] = opts
238
+ @order << [:opt, name]
239
+ end
240
+
241
+ ## Sets the version string. If set, the user can request the version
242
+ ## on the commandline. Should probably be of the form "<program name>
243
+ ## <version number>".
244
+ def version(s = nil); @version = s if s; @version end
245
+
246
+ ## Sets the usage string. If set the message will be printed as the
247
+ ## first line in the help (educate) output and ending in two new
248
+ ## lines.
249
+ def usage(s = nil) ; @usage = s if s; @usage end
250
+
251
+ ## Adds a synopsis (command summary description) right below the
252
+ ## usage line, or as the first line if usage isn't specified.
253
+ def synopsis(s = nil) ; @synopsis = s if s; @synopsis end
254
+
255
+ ## Adds text to the help display. Can be interspersed with calls to
256
+ ## #opt to build a multi-section help page.
257
+ def banner s; @order << [:text, s] end
258
+ alias :text :banner
259
+
260
+ ## Marks two (or more!) options as requiring each other. Only handles
261
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
262
+ ## better modeled with Trollop::die.
263
+ def depends *syms
264
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
265
+ @constraints << [:depends, syms]
266
+ end
267
+
268
+ ## Marks two (or more!) options as conflicting.
269
+ def conflicts *syms
270
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
271
+ @constraints << [:conflicts, syms]
272
+ end
273
+
274
+ ## Defines a set of words which cause parsing to terminate when
275
+ ## encountered, such that any options to the left of the word are
276
+ ## parsed as usual, and options to the right of the word are left
277
+ ## intact.
278
+ ##
279
+ ## A typical use case would be for subcommand support, where these
280
+ ## would be set to the list of subcommands. A subsequent Trollop
281
+ ## invocation would then be used to parse subcommand options, after
282
+ ## shifting the subcommand off of ARGV.
283
+ def stop_on *words
284
+ @stop_words = [*words].flatten
285
+ end
286
+
287
+ ## Similar to #stop_on, but stops on any unknown word when encountered
288
+ ## (unless it is a parameter for an argument). This is useful for
289
+ ## cases where you don't know the set of subcommands ahead of time,
290
+ ## i.e., without first parsing the global options.
291
+ def stop_on_unknown
292
+ @stop_on_unknown = true
293
+ end
294
+
295
+ ## Parses the commandline. Typically called by Trollop::options,
296
+ ## but you can call it directly if you need more control.
297
+ ##
298
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
299
+ def parse cmdline=ARGV
300
+ vals = {}
301
+ required = {}
302
+
303
+ opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
304
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
305
+
306
+ @specs.each do |sym, opts|
307
+ required[sym] = true if opts[:required]
308
+ vals[sym] = opts[:default]
309
+ vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil
310
+ end
311
+
312
+ resolve_default_short_options!
313
+
314
+ ## resolve symbols
315
+ given_args = {}
316
+ @leftovers = each_arg cmdline do |arg, params|
317
+ ## handle --no- forms
318
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
319
+ ["--#{$1}", true]
320
+ else
321
+ [arg, false]
322
+ end
323
+
324
+ sym = case arg
325
+ when /^-([^-])$/; @short[$1]
326
+ when /^--([^-]\S*)$/; @long[$1] || @long["no-#{$1}"]
327
+ else; raise CommandlineError, "invalid argument syntax: '#{arg}'"
328
+ end
329
+
330
+ sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
331
+
332
+ unless sym
333
+ next 0 if ignore_invalid_options
334
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
335
+ end
336
+
337
+ if given_args.include?(sym) && !@specs[sym][:multi]
338
+ raise CommandlineError, "option '#{arg}' specified multiple times"
339
+ end
340
+
341
+ given_args[sym] ||= {}
342
+ given_args[sym][:arg] = arg
343
+ given_args[sym][:negative_given] = negative_given
344
+ given_args[sym][:params] ||= []
345
+
346
+ # The block returns the number of parameters taken.
347
+ num_params_taken = 0
348
+
349
+ unless params.nil?
350
+ if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
351
+ given_args[sym][:params] << params[0, 1] # take the first parameter
352
+ num_params_taken = 1
353
+ elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
354
+ given_args[sym][:params] << params # take all the parameters
355
+ num_params_taken = params.size
356
+ end
357
+ end
358
+
359
+ num_params_taken
360
+ end
361
+
362
+ ## check for version and help args
363
+ raise VersionNeeded if given_args.include? :version
364
+ raise HelpNeeded if given_args.include? :help
365
+
366
+ ## check constraint satisfaction
367
+ @constraints.each do |type, syms|
368
+ constraint_sym = syms.find { |sym| given_args[sym] }
369
+ next unless constraint_sym
370
+
371
+ case type
372
+ when :depends
373
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
374
+ when :conflicts
375
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
376
+ end
377
+ end
378
+
379
+ required.each do |sym, val|
380
+ raise CommandlineError, "option --#{@specs[sym][:long]} must be specified" unless given_args.include? sym
381
+ end
382
+
383
+ ## parse parameters
384
+ given_args.each do |sym, given_data|
385
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
386
+
387
+ opts = @specs[sym]
388
+ if params.empty? && opts[:type] != :flag
389
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless opts[:default]
390
+ params << [opts[:default]]
391
+ end
392
+
393
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
394
+
395
+ case opts[:type]
396
+ when :flag
397
+ vals[sym] = (sym.to_s =~ /^no_/ ? negative_given : !negative_given)
398
+ when :int, :ints
399
+ vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
400
+ when :float, :floats
401
+ vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
402
+ when :string, :strings
403
+ vals[sym] = params.map { |pg| pg.map { |p| p.to_s } }
404
+ when :io, :ios
405
+ vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
406
+ when :date, :dates
407
+ vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
408
+ end
409
+
410
+ if SINGLE_ARG_TYPES.include?(opts[:type])
411
+ unless opts[:multi] # single parameter
412
+ vals[sym] = vals[sym][0][0]
413
+ else # multiple options, each with a single parameter
414
+ vals[sym] = vals[sym].map { |p| p[0] }
415
+ end
416
+ elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
417
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
418
+ end
419
+ # else: multiple options, with multiple parameters
420
+
421
+ opts[:callback].call(vals[sym]) if opts.has_key?(:callback)
422
+ end
423
+
424
+ ## modify input in place with only those
425
+ ## arguments we didn't process
426
+ cmdline.clear
427
+ @leftovers.each { |l| cmdline << l }
428
+
429
+ ## allow openstruct-style accessors
430
+ class << vals
431
+ def method_missing(m, *args)
432
+ self[m] || self[m.to_s]
433
+ end
434
+ end
435
+ vals
436
+ end
437
+
438
+ def parse_date_parameter param, arg #:nodoc:
439
+ begin
440
+ begin
441
+ require 'chronic'
442
+ time = Chronic.parse(param)
443
+ rescue NameError
444
+ # chronic is not available
445
+ end
446
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
447
+ rescue ArgumentError
448
+ raise CommandlineError, "option '#{arg}' needs a date"
449
+ end
450
+ end
451
+
452
+ ## Print the help message to +stream+.
453
+ def educate stream=$stdout
454
+ width # hack: calculate it now; otherwise we have to be careful not to
455
+ # call this unless the cursor's at the beginning of a line.
456
+ left = {}
457
+ @specs.each do |name, spec|
458
+ left[name] =
459
+ (spec[:short] && spec[:short] != :none ? "-#{spec[:short]}" : "") +
460
+ (spec[:short] && spec[:short] != :none ? ", " : "") + "--#{spec[:long]}" +
461
+ case spec[:type]
462
+ when :flag; ""
463
+ when :int; "=<i>"
464
+ when :ints; "=<i+>"
465
+ when :string; "=<s>"
466
+ when :strings; "=<s+>"
467
+ when :float; "=<f>"
468
+ when :floats; "=<f+>"
469
+ when :io; "=<filename/uri>"
470
+ when :ios; "=<filename/uri+>"
471
+ when :date; "=<date>"
472
+ when :dates; "=<date+>"
473
+ end +
474
+ (spec[:type] == :flag && spec[:default] ? ", --no-#{spec[:long]}" : "")
475
+ end
476
+
477
+ leftcol_width = left.values.map { |s| s.length }.max || 0
478
+ rightcol_start = leftcol_width + 6 # spaces
479
+
480
+ unless @order.size > 0 && @order.first.first == :text
481
+ command_name = File.basename($0).gsub(/\.[^.]+$/, '')
482
+ stream.puts "Usage: #{command_name} #@usage\n" if @usage
483
+ stream.puts "#@synopsis\n" if @synopsis
484
+ stream.puts if @usage or @synopsis
485
+ stream.puts "#@version\n" if @version
486
+ stream.puts "Options:"
487
+ end
488
+
489
+ @order.each do |what, opt|
490
+ if what == :text
491
+ stream.puts wrap(opt)
492
+ next
493
+ end
494
+
495
+ spec = @specs[opt]
496
+ stream.printf " %-#{leftcol_width}s ", left[opt]
497
+ desc = spec[:desc] + begin
498
+ default_s = case spec[:default]
499
+ when $stdout; "<stdout>"
500
+ when $stdin; "<stdin>"
501
+ when $stderr; "<stderr>"
502
+ when Array
503
+ spec[:default].join(", ")
504
+ else
505
+ spec[:default].to_s
506
+ end
507
+
508
+ if spec[:default]
509
+ if spec[:desc] =~ /\.$/
510
+ " (Default: #{default_s})"
511
+ else
512
+ " (default: #{default_s})"
513
+ end
514
+ else
515
+ ""
516
+ end
517
+ end
518
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
519
+ end
520
+ end
521
+
522
+ def width #:nodoc:
523
+ @width ||= if $stdout.tty?
524
+ begin
525
+ require 'io/console'
526
+ IO.console.winsize.last
527
+ rescue LoadError
528
+ legacy_width
529
+ end
530
+ else
531
+ 80
532
+ end
533
+ end
534
+
535
+ def legacy_width
536
+ # Support for older Rubies where io/console is not available
537
+ `tput cols`.to_i
538
+ rescue Errno::ENOENT
539
+ 80
540
+ end
541
+ private :legacy_width
542
+
543
+ def wrap str, opts={} # :nodoc:
544
+ if str == ""
545
+ [""]
546
+ else
547
+ inner = false
548
+ str.split("\n").map do |s|
549
+ line = wrap_line s, opts.merge(:inner => inner)
550
+ inner = true
551
+ line
552
+ end.flatten
553
+ end
554
+ end
555
+
556
+ ## The per-parser version of Trollop::die (see that for documentation).
557
+ def die arg, msg
558
+ if msg
559
+ $stderr.puts "Error: argument --#{@specs[arg][:long]} #{msg}."
560
+ else
561
+ $stderr.puts "Error: #{arg}."
562
+ end
563
+ $stderr.puts "Try --help for help."
564
+ exit(-1)
565
+ end
566
+
567
+ private
568
+
569
+ ## yield successive arg, parameter pairs
570
+ def each_arg args
571
+ remains = []
572
+ i = 0
573
+
574
+ until i >= args.length
575
+ if @stop_words.member? args[i]
576
+ remains += args[i .. -1]
577
+ return remains
578
+ end
579
+ case args[i]
580
+ when /^--$/ # arg terminator
581
+ remains += args[(i + 1) .. -1]
582
+ return remains
583
+ when /^--(\S+?)=(.*)$/ # long argument with equals
584
+ yield "--#{$1}", [$2]
585
+ i += 1
586
+ when /^--(\S+)$/ # long argument
587
+ params = collect_argument_parameters(args, i + 1)
588
+ unless params.empty?
589
+ num_params_taken = yield args[i], params
590
+ unless num_params_taken
591
+ if @stop_on_unknown
592
+ remains += args[i + 1 .. -1]
593
+ return remains
594
+ else
595
+ remains += params
596
+ end
597
+ end
598
+ i += 1 + num_params_taken
599
+ else # long argument no parameter
600
+ yield args[i], nil
601
+ i += 1
602
+ end
603
+ when /^-(\S+)$/ # one or more short arguments
604
+ shortargs = $1.split(//)
605
+ shortargs.each_with_index do |a, j|
606
+ if j == (shortargs.length - 1)
607
+ params = collect_argument_parameters(args, i + 1)
608
+ unless params.empty?
609
+ num_params_taken = yield "-#{a}", params
610
+ unless num_params_taken
611
+ if @stop_on_unknown
612
+ remains += args[i + 1 .. -1]
613
+ return remains
614
+ else
615
+ remains += params
616
+ end
617
+ end
618
+ i += 1 + num_params_taken
619
+ else # argument no parameter
620
+ yield "-#{a}", nil
621
+ i += 1
622
+ end
623
+ else
624
+ yield "-#{a}", nil
625
+ end
626
+ end
627
+ else
628
+ if @stop_on_unknown
629
+ remains += args[i .. -1]
630
+ return remains
631
+ else
632
+ remains << args[i]
633
+ i += 1
634
+ end
635
+ end
636
+ end
637
+
638
+ remains
639
+ end
640
+
641
+ def parse_integer_parameter param, arg
642
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^-?[\d_]+$/
643
+ param.to_i
644
+ end
645
+
646
+ def parse_float_parameter param, arg
647
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
648
+ param.to_f
649
+ end
650
+
651
+ def parse_io_parameter param, arg
652
+ case param
653
+ when /^(stdin|-)$/i; $stdin
654
+ else
655
+ require 'open-uri'
656
+ begin
657
+ open param
658
+ rescue SystemCallError => e
659
+ raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
660
+ end
661
+ end
662
+ end
663
+
664
+ def collect_argument_parameters args, start_at
665
+ params = []
666
+ pos = start_at
667
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
668
+ params << args[pos]
669
+ pos += 1
670
+ end
671
+ params
672
+ end
673
+
674
+ def resolve_default_short_options!
675
+ @order.each do |type, name|
676
+ next unless type == :opt
677
+ opts = @specs[name]
678
+ next if opts[:short]
679
+
680
+ c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
681
+ if c # found a character to use
682
+ opts[:short] = c
683
+ @short[c] = name
684
+ end
685
+ end
686
+ end
687
+
688
+ def wrap_line str, opts={}
689
+ prefix = opts[:prefix] || 0
690
+ width = opts[:width] || (self.width - 1)
691
+ start = 0
692
+ ret = []
693
+ until start > str.length
694
+ nextt =
695
+ if start + width >= str.length
696
+ str.length
697
+ else
698
+ x = str.rindex(/\s/, start + width)
699
+ x = str.index(/\s/, start) if x && x < start
700
+ x || str.length
701
+ end
702
+ ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start ... nextt]
703
+ start = nextt + 1
704
+ end
705
+ ret
706
+ end
707
+
708
+ ## instance_eval but with ability to handle block arguments
709
+ ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
710
+ def cloaker &b
711
+ (class << self; self; end).class_eval do
712
+ define_method :cloaker_, &b
713
+ meth = instance_method :cloaker_
714
+ remove_method :cloaker_
715
+ meth
716
+ end
717
+ end
718
+ end
719
+
720
+ ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser,
721
+ ## passes the block to it, then parses +args+ with it, handling any errors or
722
+ ## requests for help or version information appropriately (and then exiting).
723
+ ## Modifies +args+ in place. Returns a hash of option values.
724
+ ##
725
+ ## The block passed in should contain zero or more calls to +opt+
726
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
727
+ ## probably a call to +version+ (Parser#version).
728
+ ##
729
+ ## The returned block contains a value for every option specified with
730
+ ## +opt+. The value will be the value given on the commandline, or the
731
+ ## default value if the option was not specified on the commandline. For
732
+ ## every option specified on the commandline, a key "<option
733
+ ## name>_given" will also be set in the hash.
734
+ ##
735
+ ## Example:
736
+ ##
737
+ ## require 'trollop'
738
+ ## opts = Trollop::options do
739
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
740
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
741
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
742
+ ## end
743
+ ##
744
+ ## ## if called with no arguments
745
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
746
+ ##
747
+ ## ## if called with --monkey
748
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
749
+ ##
750
+ ## See more examples at http://trollop.rubyforge.org.
751
+ def options args=ARGV, *a, &b
752
+ @last_parser = Parser.new(*a, &b)
753
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
754
+ end
755
+
756
+ ## If Trollop::options doesn't do quite what you want, you can create a Parser
757
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
758
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
759
+ ## have these handled for you in the standard manner (e.g. show the help
760
+ ## and then exit upon an HelpNeeded exception), call your code from within
761
+ ## a block passed to this method.
762
+ ##
763
+ ## Note that this method will call System#exit after handling an exception!
764
+ ##
765
+ ## Usage example:
766
+ ##
767
+ ## require 'trollop'
768
+ ## p = Trollop::Parser.new do
769
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
770
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
771
+ ## end
772
+ ##
773
+ ## opts = Trollop::with_standard_exception_handling p do
774
+ ## o = p.parse ARGV
775
+ ## raise Trollop::HelpNeeded if ARGV.empty? # show help screen
776
+ ## o
777
+ ## end
778
+ ##
779
+ ## Requires passing in the parser object.
780
+
781
+ def with_standard_exception_handling parser
782
+ begin
783
+ yield
784
+ rescue CommandlineError => e
785
+ $stderr.puts "Error: #{e.message}."
786
+ $stderr.puts "Try --help for help."
787
+ exit(-1)
788
+ rescue HelpNeeded
789
+ parser.educate
790
+ exit
791
+ rescue VersionNeeded
792
+ puts parser.version
793
+ exit
794
+ end
795
+ end
796
+
797
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
798
+ ## 'msg', and dies. Example:
799
+ ##
800
+ ## options do
801
+ ## opt :volume, :default => 0.0
802
+ ## end
803
+ ##
804
+ ## die :volume, "too loud" if opts[:volume] > 10.0
805
+ ## die :volume, "too soft" if opts[:volume] < 0.1
806
+ ##
807
+ ## In the one-argument case, simply print that message, a notice
808
+ ## about -h, and die. Example:
809
+ ##
810
+ ## options do
811
+ ## opt :whatever # ...
812
+ ## end
813
+ ##
814
+ ## Trollop::die "need at least one filename" if ARGV.empty?
815
+ def die arg, msg=nil
816
+ if @last_parser
817
+ @last_parser.die arg, msg
818
+ else
819
+ raise ArgumentError, "Trollop::die can only be called after Trollop::options"
820
+ end
821
+ end
822
+
823
+ ## Displays the help message and dies. Example:
824
+ ##
825
+ ## options do
826
+ ## opt :volume, :default => 0.0
827
+ ## banner <<-EOS
828
+ ## Usage:
829
+ ## #$0 [options] <name>
830
+ ## where [options] are:
831
+ ## EOS
832
+ ## end
833
+ ##
834
+ ## Trollop::educate if ARGV.empty?
835
+ def educate
836
+ if @last_parser
837
+ @last_parser.educate
838
+ exit
839
+ else
840
+ raise ArgumentError, "Trollop::educate can only be called after Trollop::options"
841
+ end
842
+ end
843
+
844
+ module_function :options, :die, :educate, :with_standard_exception_handling
845
+
846
+ end # module
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kajiki
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ken J.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple gem to build daemons
14
+ email:
15
+ - kenjij@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - kajiki.gemspec
23
+ - lib/kajiki.rb
24
+ - lib/kajiki/handler.rb
25
+ - lib/kajiki/presets.rb
26
+ - lib/kajiki/runner.rb
27
+ - lib/kajiki/trollop.rb
28
+ homepage: https://github.com/kenjij/kajiki
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.4.6
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: A simple Ruby gem to ease building daemons.
52
+ test_files: []
53
+ has_rdoc: