hostgitrb 0.0.1

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.textile ADDED
@@ -0,0 +1,32 @@
1
+ h1. HostGitRb
2
+
3
+ Some simple scripts to help give people access to git repositories without giving them full access via SSH
4
+ The directory that contains these scripts should be added to the PATH variable so they can be easily accessed.
5
+
6
+ h2. only_git.rb
7
+
8
+ This script is not directly used. It's used by SSH because of the command="" line that is inserted in the ~/.ssh/authorized_keys file.
9
+ Rather than manually adding these entries to the file, the @hostgitrb@ script can be used.
10
+
11
+ h2. hostgitrb
12
+
13
+ Adds the proper SSH command="" lines to the authorized_keys file to give access to other people.
14
+ Use the -h argument to see the options.
15
+
16
+ h2. Example
17
+
18
+ Imagine I have a directory called /home/user/myrepos that has 2 git repos called one.git and two.git
19
+ Now I want to allow a friend of mine to have access to those 2 repos but I don't want him to be able to login via SSH to my server.
20
+
21
+ So I ask him for his public ssh key, and I execute the command
22
+ <pre>./allow_git.rb -d /home/user/myrepos -f /home/user/keys/friend_rsa.pub</pre>
23
+
24
+ Or the actual key can be passed as an argument, just don't forget the "" because of the spaces:
25
+ <pre>./allow_git.rb -d /home/user/myrepos -k "ssh-rsa A.....w== user@host"</pre>
26
+
27
+ This adds a line to ~/.ssh/authorized_keys and gives him push and pull access to repos one.git and two.git like so:
28
+ <pre>git clone user@server.com:one.git
29
+ git push origin master
30
+ git fetch
31
+ git pull
32
+ etc.</pre>
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "mg"
2
+ MG.new("hostgitrb.gemspec")
data/bin/hostgitrb ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+ require 'ftools'
5
+ require File.join(File.dirname(__FILE__), '..', 'vendor', 'trollop.rb')
6
+
7
+ opts = Trollop.options do
8
+ opt :file, 'Set path to public ssh key file', :default => ''
9
+ opt :key, 'Provide public ssh key as a string', :default => ''
10
+ opt :dir, 'Set full path to directory with git repositories to allow access to', :default => ''
11
+ opt :readonly, 'Set access to repositories in --dir to read only', :default => false
12
+ opt :nobackup, 'Don\'t make backup of authorized_keys file', :default => false
13
+ opt :authorizedkeys, 'Set authorized_keys file', :default => File.expand_path('~/.ssh/authorized_keys')
14
+ end
15
+ Trollop::die 'Invalid directory' unless File.directory?(opts[:dir])
16
+ Trollop::die 'No public ssh key provided' if opts[:key] == '' && !File.exists?(opts[:file])
17
+
18
+ if File.exists?(opts[:file]) then
19
+ ssh_key = File.readlines(opts[:file])[0]
20
+ else
21
+ ssh_key = opts[:key]
22
+ end
23
+
24
+ if File.exists?(opts[:authorizedkeys]) && !opts[:nobackup] then
25
+ backup_file = opts[:authorizedkeys] + '.backup'
26
+ count = 2
27
+ while(File.exists?(backup_file)) do
28
+ backup_file = opts[:authorizedkeys] + ".backup#{count}"
29
+ count += 1
30
+ end
31
+ File.copy(opts[:authorizedkeys], backup_file)
32
+ end
33
+
34
+ only_git_cmd = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'only_git.rb')) + " --dir #{opts[:dir]}"
35
+ only_git_cmd << " --readonly" if opts[:readonly]
36
+
37
+ ssh_inf = "\n" if File.exists?(opts[:authorizedkeys]) && File.readlines(opts[:authorizedkeys]).last != "\n"
38
+ ssh_inf << "#\n"
39
+ ssh_inf << "# only_git: #{opts[:readonly] ? 'Read-only' : 'R/W'} access to #{opts[:dir]}\n"
40
+ ssh_inf << "#\n"
41
+
42
+ ssh_cmd = "command=\"#{only_git_cmd}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{ssh_key}"
43
+
44
+ File.open(opts[:authorizedkeys], 'a') { |f| f.write(ssh_inf + ssh_cmd) }
data/lib/only_git.rb ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+ require File.join(File.dirname(__FILE__), '..', 'vendor', 'trollop.rb')
5
+
6
+ DEBUG_LEVEL = Logger::ERROR
7
+
8
+ #Examples of commands that are permitted and that are used by git (git clone/git fetch/git push/git pull)
9
+ # git-upload-pack '/home/user/repo/Notes.git'
10
+ # git-receive-pack '/home/user/repo/Notes.git'
11
+ # git-upload-pack 'Notes.git'
12
+ GIT_R_REGEX = /^git[\-](upload)[\-]pack '([a-zA-Z\-_\/]+)([.]git)?'$/
13
+ GIT_RW_REGEX = /^git[\-](upload|receive)[\-]pack '([a-zA-Z\-_\/]+)([.]git)?'$/
14
+
15
+ opts = Trollop::options do
16
+ opt :log, "Set log file", :default => File.join(File.dirname(__FILE__), 'debug.log')
17
+ opt :dir, "Set directory that contains git repositories", :default => ''
18
+ opt :readonly, "Set access to repositories under --dir to read only", :default => false
19
+ end
20
+ Trollop::die 'Directory with git repositories doesn\'t exist. Needs to be set with --dir' unless File.directory? opts[:dir]
21
+
22
+ logger = Logger.new(opts[:log], 'weekly')
23
+ logger.level = DEBUG_LEVEL
24
+
25
+ command = String.new(ENV['SSH_ORIGINAL_COMMAND'])
26
+ logger.debug("Received command: #{command}")
27
+
28
+ right_command = GIT_RW_REGEX
29
+ right_command = GIT_R_REGEX if opts[:readonly]
30
+ logger.debug('Access to repos under dir is set to: ' + (opts[:readonly] ? 'Read' : 'Read/Write'))
31
+
32
+ if command =~ right_command then
33
+ opts[:dir] = File.join(opts[:dir], '')
34
+ command.gsub!("-pack '", "-pack '#{opts[:dir]}")
35
+ logger.info("Executing command: #{command}")
36
+ exec command
37
+ else
38
+ logger.error("Received bad command")
39
+ exec 'echo NOT ALLOWED'
40
+ end
41
+
42
+
data/vendor/trollop.rb ADDED
@@ -0,0 +1,714 @@
1
+ ## lib/trollop.rb -- trollop command-line processing library
2
+ ## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net)
3
+ ## Copyright:: Copyright 2007 William Morgan
4
+ ## License:: GNU GPL version 2
5
+
6
+ module Trollop
7
+
8
+ VERSION = "1.12"
9
+
10
+ ## Thrown by Parser in the event of a commandline error. Not needed if
11
+ ## you're using the Trollop::options entry.
12
+ class CommandlineError < StandardError; end
13
+
14
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
15
+ ## automatically by Trollop#options.
16
+ class HelpNeeded < StandardError; end
17
+
18
+ ## Thrown by Parser if the user passes in '-h' or '--version'. Handled
19
+ ## automatically by Trollop#options.
20
+ class VersionNeeded < StandardError; end
21
+
22
+ ## Regex for floating point numbers
23
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))$/
24
+
25
+ ## Regex for parameters
26
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
27
+
28
+ ## The commandline parser. In typical usage, the methods in this class
29
+ ## will be handled internally by Trollop::options. In this case, only the
30
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
31
+ ## typically be called.
32
+ ##
33
+ ## If it's necessary to instantiate this class (for more complicated
34
+ ## argument-parsing situations), be sure to call #parse to actually
35
+ ## produce the output hash.
36
+ class Parser
37
+
38
+ ## The set of values that indicate a flag option when passed as the
39
+ ## +:type+ parameter of #opt.
40
+ FLAG_TYPES = [:flag, :bool, :boolean]
41
+
42
+ ## The set of values that indicate a single-parameter option when
43
+ ## passed as the +:type+ parameter of #opt.
44
+ ##
45
+ ## A value of +io+ corresponds to a readable IO resource, including
46
+ ## a filename, URI, or the strings 'stdin' or '-'.
47
+ SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io]
48
+
49
+ ## The set of values that indicate a multiple-parameter option when
50
+ ## passed as the +:type+ parameter of #opt.
51
+ MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios]
52
+
53
+ ## The complete set of legal values for the +:type+ parameter of #opt.
54
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
55
+
56
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
57
+
58
+ ## The values from the commandline that were not interpreted by #parse.
59
+ attr_reader :leftovers
60
+
61
+ ## The complete configuration hashes for each option. (Mainly useful
62
+ ## for testing.)
63
+ attr_reader :specs
64
+
65
+ ## Initializes the parser, and instance-evaluates any block given.
66
+ def initialize *a, &b
67
+ @version = nil
68
+ @leftovers = []
69
+ @specs = {}
70
+ @long = {}
71
+ @short = {}
72
+ @order = []
73
+ @constraints = []
74
+ @stop_words = []
75
+ @stop_on_unknown = false
76
+
77
+ #instance_eval(&b) if b # can't take arguments
78
+ cloaker(&b).bind(self).call(*a) if b
79
+ end
80
+
81
+ ## Define an option. +name+ is the option name, a unique identifier
82
+ ## for the option that you will use internally, which should be a
83
+ ## symbol or a string. +desc+ is a string description which will be
84
+ ## displayed in help messages.
85
+ ##
86
+ ## Takes the following optional arguments:
87
+ ##
88
+ ## [+: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.
89
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+.
90
+ ## [+: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.
91
+ ## [+: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+.
92
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
93
+ ## [+: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.)
94
+ ##
95
+ ## Note that there are two types of argument multiplicity: an argument
96
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
97
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
98
+ ##
99
+ ## Arguments that take multiple values should have a +:type+ parameter
100
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
101
+ ## value of an array of the correct type (e.g. [String]). The
102
+ ## value of this argument will be an array of the parameters on the
103
+ ## commandline.
104
+ ##
105
+ ## Arguments that can occur multiple times should be marked with
106
+ ## +:multi+ => +true+. The value of this argument will also be an array.
107
+ ##
108
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
109
+ ## +:multi+ => +true+), in which case the value of the argument will be
110
+ ## an array of arrays.
111
+ ##
112
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
113
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
114
+ ## is a multi-value argument as well as a multi-occurrence argument.
115
+ ## In thise case, Trollop assumes that it's not a multi-value argument.
116
+ ## If you want a multi-value, multi-occurrence argument with a default
117
+ ## value, you must specify +:type+ as well.
118
+
119
+ def opt name, desc="", opts={}
120
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
121
+
122
+ ## fill in :type
123
+ opts[:type] = # normalize
124
+ case opts[:type]
125
+ when :boolean, :bool; :flag
126
+ when :integer; :int
127
+ when :integers; :ints
128
+ when :double; :float
129
+ when :doubles; :floats
130
+ when Class
131
+ case opts[:type].to_s # sigh... there must be a better way to do this
132
+ when 'TrueClass', 'FalseClass'; :flag
133
+ when 'String'; :string
134
+ when 'Integer'; :int
135
+ when 'Float'; :float
136
+ when 'IO'; :io
137
+ else
138
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
139
+ end
140
+ when nil; nil
141
+ else
142
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
143
+ opts[:type]
144
+ end
145
+
146
+ ## for options with :multi => true, an array default doesn't imply
147
+ ## a multi-valued argument. for that you have to specify a :type
148
+ ## as well. (this is how we disambiguate an ambiguous situation;
149
+ ## see the docs for Parser#opt for details.)
150
+ disambiguated_default =
151
+ if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type]
152
+ opts[:default].first
153
+ else
154
+ opts[:default]
155
+ end
156
+
157
+ type_from_default =
158
+ case disambiguated_default
159
+ when Integer; :int
160
+ when Numeric; :float
161
+ when TrueClass, FalseClass; :flag
162
+ when String; :string
163
+ when IO; :io
164
+ when Array
165
+ if opts[:default].empty?
166
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
167
+ end
168
+ case opts[:default][0] # the first element determines the types
169
+ when Integer; :ints
170
+ when Numeric; :floats
171
+ when String; :strings
172
+ when IO; :ios
173
+ else
174
+ raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
175
+ end
176
+ when nil; nil
177
+ else
178
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
179
+ end
180
+
181
+ raise ArgumentError, ":type specification and default type don't match" if opts[:type] && type_from_default && opts[:type] != type_from_default
182
+
183
+ opts[:type] = opts[:type] || type_from_default || :flag
184
+
185
+ ## fill in :long
186
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
187
+ opts[:long] =
188
+ case opts[:long]
189
+ when /^--([^-].*)$/
190
+ $1
191
+ when /^[^-]/
192
+ opts[:long]
193
+ else
194
+ raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
195
+ end
196
+ raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
197
+
198
+ ## fill in :short
199
+ opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none
200
+ opts[:short] = case opts[:short]
201
+ when /^-(.)$/; $1
202
+ when nil, :none, /^.$/; opts[:short]
203
+ else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
204
+ end
205
+
206
+ if opts[:short]
207
+ raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
208
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
209
+ end
210
+
211
+ ## fill in :default for flags
212
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
213
+
214
+ ## autobox :default for :multi (multi-occurrence) arguments
215
+ opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array)
216
+
217
+ ## fill in :multi
218
+ opts[:multi] ||= false
219
+
220
+ opts[:desc] ||= desc
221
+ @long[opts[:long]] = name
222
+ @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
223
+ @specs[name] = opts
224
+ @order << [:opt, name]
225
+ end
226
+
227
+ ## Sets the version string. If set, the user can request the version
228
+ ## on the commandline. Should probably be of the form "<program name>
229
+ ## <version number>".
230
+ def version s=nil; @version = s if s; @version end
231
+
232
+ ## Adds text to the help display. Can be interspersed with calls to
233
+ ## #opt to build a multi-section help page.
234
+ def banner s; @order << [:text, s] end
235
+ alias :text :banner
236
+
237
+ ## Marks two (or more!) options as requiring each other. Only handles
238
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
239
+ ## better modeled with Trollop::die.
240
+ def depends *syms
241
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
242
+ @constraints << [:depends, syms]
243
+ end
244
+
245
+ ## Marks two (or more!) options as conflicting.
246
+ def conflicts *syms
247
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
248
+ @constraints << [:conflicts, syms]
249
+ end
250
+
251
+ ## Defines a set of words which cause parsing to terminate when
252
+ ## encountered, such that any options to the left of the word are
253
+ ## parsed as usual, and options to the right of the word are left
254
+ ## intact.
255
+ ##
256
+ ## A typical use case would be for subcommand support, where these
257
+ ## would be set to the list of subcommands. A subsequent Trollop
258
+ ## invocation would then be used to parse subcommand options, after
259
+ ## shifting the subcommand off of ARGV.
260
+ def stop_on *words
261
+ @stop_words = [*words].flatten
262
+ end
263
+
264
+ ## Similar to #stop_on, but stops on any unknown word when encountered
265
+ ## (unless it is a parameter for an argument). This is useful for
266
+ ## cases where you don't know the set of subcommands ahead of time,
267
+ ## i.e., without first parsing the global options.
268
+ def stop_on_unknown
269
+ @stop_on_unknown = true
270
+ end
271
+
272
+ ## Parses the commandline. Typically called by Trollop::options.
273
+ def parse cmdline=ARGV
274
+ vals = {}
275
+ required = {}
276
+
277
+ opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
278
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
279
+
280
+ @specs.each do |sym, opts|
281
+ required[sym] = true if opts[:required]
282
+ vals[sym] = opts[:default]
283
+ end
284
+
285
+ resolve_default_short_options
286
+
287
+ ## resolve symbols
288
+ given_args = {}
289
+ @leftovers = each_arg cmdline do |arg, params|
290
+ sym =
291
+ case arg
292
+ when /^-([^-])$/
293
+ @short[$1]
294
+ when /^--([^-]\S*)$/
295
+ @long[$1]
296
+ else
297
+ raise CommandlineError, "invalid argument syntax: '#{arg}'"
298
+ end
299
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
300
+
301
+ if given_args.include?(sym) && !@specs[sym][:multi]
302
+ raise CommandlineError, "option '#{arg}' specified multiple times"
303
+ end
304
+
305
+ given_args[sym] ||= {}
306
+
307
+ given_args[sym][:arg] = arg
308
+ given_args[sym][:params] ||= []
309
+
310
+ # The block returns the number of parameters taken.
311
+ num_params_taken = 0
312
+
313
+ unless params.nil?
314
+ if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
315
+ given_args[sym][:params] << params[0, 1] # take the first parameter
316
+ num_params_taken = 1
317
+ elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
318
+ given_args[sym][:params] << params # take all the parameters
319
+ num_params_taken = params.size
320
+ end
321
+ end
322
+
323
+ num_params_taken
324
+ end
325
+
326
+ ## check for version and help args
327
+ raise VersionNeeded if given_args.include? :version
328
+ raise HelpNeeded if given_args.include? :help
329
+
330
+ ## check constraint satisfaction
331
+ @constraints.each do |type, syms|
332
+ constraint_sym = syms.find { |sym| given_args[sym] }
333
+ next unless constraint_sym
334
+
335
+ case type
336
+ when :depends
337
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
338
+ when :conflicts
339
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
340
+ end
341
+ end
342
+
343
+ required.each do |sym, val|
344
+ raise CommandlineError, "option '#{sym}' must be specified" unless given_args.include? sym
345
+ end
346
+
347
+ ## parse parameters
348
+ given_args.each do |sym, given_data|
349
+ arg = given_data[:arg]
350
+ params = given_data[:params]
351
+
352
+ opts = @specs[sym]
353
+ raise CommandlineError, "option '#{arg}' needs a parameter" if params.empty? && opts[:type] != :flag
354
+
355
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
356
+
357
+ case opts[:type]
358
+ when :flag
359
+ vals[sym] = !opts[:default]
360
+ when :int, :ints
361
+ vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
362
+ when :float, :floats
363
+ vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
364
+ when :string, :strings
365
+ vals[sym] = params.map { |pg| pg.map { |p| p.to_s } }
366
+ when :io, :ios
367
+ vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
368
+ end
369
+
370
+ if SINGLE_ARG_TYPES.include?(opts[:type])
371
+ unless opts[:multi] # single parameter
372
+ vals[sym] = vals[sym][0][0]
373
+ else # multiple options, each with a single parameter
374
+ vals[sym] = vals[sym].map { |p| p[0] }
375
+ end
376
+ elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
377
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
378
+ end
379
+ # else: multiple options, with multiple parameters
380
+ end
381
+
382
+ ## allow openstruct-style accessors
383
+ class << vals
384
+ def method_missing(m, *args)
385
+ self[m] || self[m.to_s]
386
+ end
387
+ end
388
+ vals
389
+ end
390
+
391
+ ## Print the help message to +stream+.
392
+ def educate stream=$stdout
393
+ width # just calculate it now; otherwise we have to be careful not to
394
+ # call this unless the cursor's at the beginning of a line.
395
+
396
+ left = {}
397
+ @specs.each do |name, spec|
398
+ left[name] = "--#{spec[:long]}" +
399
+ (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") +
400
+ case spec[:type]
401
+ when :flag; ""
402
+ when :int; " <i>"
403
+ when :ints; " <i+>"
404
+ when :string; " <s>"
405
+ when :strings; " <s+>"
406
+ when :float; " <f>"
407
+ when :floats; " <f+>"
408
+ when :io; " <filename/uri>"
409
+ when :ios; " <filename/uri+>"
410
+ end
411
+ end
412
+
413
+ leftcol_width = left.values.map { |s| s.length }.max || 0
414
+ rightcol_start = leftcol_width + 6 # spaces
415
+
416
+ unless @order.size > 0 && @order.first.first == :text
417
+ stream.puts "#@version\n" if @version
418
+ stream.puts "Options:"
419
+ end
420
+
421
+ @order.each do |what, opt|
422
+ if what == :text
423
+ stream.puts wrap(opt)
424
+ next
425
+ end
426
+
427
+ spec = @specs[opt]
428
+ stream.printf " %#{leftcol_width}s: ", left[opt]
429
+ desc = spec[:desc] + begin
430
+ default_s = case spec[:default]
431
+ when $stdout; "<stdout>"
432
+ when $stdin; "<stdin>"
433
+ when $stderr; "<stderr>"
434
+ when Array
435
+ spec[:default].join(", ")
436
+ else
437
+ spec[:default].to_s
438
+ end
439
+
440
+ if spec[:default]
441
+ if spec[:desc] =~ /\.$/
442
+ " (Default: #{default_s})"
443
+ else
444
+ " (default: #{default_s})"
445
+ end
446
+ else
447
+ ""
448
+ end
449
+ end
450
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
451
+ end
452
+ end
453
+
454
+ def width #:nodoc:
455
+ @width ||= if $stdout.tty?
456
+ begin
457
+ require 'curses'
458
+ Curses::init_screen
459
+ x = Curses::cols
460
+ Curses::close_screen
461
+ x
462
+ rescue Exception
463
+ 80
464
+ end
465
+ else
466
+ 80
467
+ end
468
+ end
469
+
470
+ def wrap str, opts={} # :nodoc:
471
+ if str == ""
472
+ [""]
473
+ else
474
+ str.split("\n").map { |s| wrap_line s, opts }.flatten
475
+ end
476
+ end
477
+
478
+ private
479
+
480
+ ## yield successive arg, parameter pairs
481
+ def each_arg args
482
+ remains = []
483
+ i = 0
484
+
485
+ until i >= args.length
486
+ if @stop_words.member? args[i]
487
+ remains += args[i .. -1]
488
+ return remains
489
+ end
490
+ case args[i]
491
+ when /^--$/ # arg terminator
492
+ remains += args[(i + 1) .. -1]
493
+ return remains
494
+ when /^--(\S+?)=(\S+)$/ # long argument with equals
495
+ yield "--#{$1}", [$2]
496
+ i += 1
497
+ when /^--(\S+)$/ # long argument
498
+ params = collect_argument_parameters(args, i + 1)
499
+ unless params.empty?
500
+ num_params_taken = yield args[i], params
501
+ unless num_params_taken
502
+ if @stop_on_unknown
503
+ remains += args[i + 1 .. -1]
504
+ return remains
505
+ else
506
+ remains += params
507
+ end
508
+ end
509
+ i += 1 + num_params_taken
510
+ else # long argument no parameter
511
+ yield args[i], nil
512
+ i += 1
513
+ end
514
+ when /^-(\S+)$/ # one or more short arguments
515
+ shortargs = $1.split(//)
516
+ shortargs.each_with_index do |a, j|
517
+ if j == (shortargs.length - 1)
518
+ params = collect_argument_parameters(args, i + 1)
519
+ unless params.empty?
520
+ num_params_taken = yield "-#{a}", params
521
+ unless num_params_taken
522
+ if @stop_on_unknown
523
+ remains += args[i + 1 .. -1]
524
+ return remains
525
+ else
526
+ remains += params
527
+ end
528
+ end
529
+ i += 1 + num_params_taken
530
+ else # argument no parameter
531
+ yield "-#{a}", nil
532
+ i += 1
533
+ end
534
+ else
535
+ yield "-#{a}", nil
536
+ end
537
+ end
538
+ else
539
+ if @stop_on_unknown
540
+ remains += args[i .. -1]
541
+ return remains
542
+ else
543
+ remains << args[i]
544
+ i += 1
545
+ end
546
+ end
547
+ end
548
+
549
+ remains
550
+ end
551
+
552
+ def parse_integer_parameter param, arg
553
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/
554
+ param.to_i
555
+ end
556
+
557
+ def parse_float_parameter param, arg
558
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
559
+ param.to_f
560
+ end
561
+
562
+ def parse_io_parameter param, arg
563
+ case param
564
+ when /^(stdin|-)$/i; $stdin
565
+ else
566
+ require 'open-uri'
567
+ begin
568
+ open param
569
+ rescue SystemCallError => e
570
+ raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
571
+ end
572
+ end
573
+ end
574
+
575
+ def collect_argument_parameters args, start_at
576
+ params = []
577
+ pos = start_at
578
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
579
+ params << args[pos]
580
+ pos += 1
581
+ end
582
+ params
583
+ end
584
+
585
+ def resolve_default_short_options
586
+ @order.each do |type, name|
587
+ next unless type == :opt
588
+ opts = @specs[name]
589
+ next if opts[:short]
590
+
591
+ c = opts[:long].split(//).find { |c| c !~ INVALID_SHORT_ARG_REGEX && !@short.member?(c) }
592
+ raise ArgumentError, "can't generate a default short option name for #{opts[:long].inspect}: out of unique characters" unless c
593
+
594
+ opts[:short] = c
595
+ @short[c] = name
596
+ end
597
+ end
598
+
599
+ def wrap_line str, opts={}
600
+ prefix = opts[:prefix] || 0
601
+ width = opts[:width] || (self.width - 1)
602
+ start = 0
603
+ ret = []
604
+ until start > str.length
605
+ nextt =
606
+ if start + width >= str.length
607
+ str.length
608
+ else
609
+ x = str.rindex(/\s/, start + width)
610
+ x = str.index(/\s/, start) if x && x < start
611
+ x || str.length
612
+ end
613
+ ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt]
614
+ start = nextt + 1
615
+ end
616
+ ret
617
+ end
618
+
619
+ ## instance_eval but with ability to handle block arguments
620
+ ## thanks to why: http://redhanded.hobix.com/inspect/aBlockCostume.html
621
+ def cloaker &b
622
+ (class << self; self; end).class_eval do
623
+ define_method :cloaker_, &b
624
+ meth = instance_method :cloaker_
625
+ remove_method :cloaker_
626
+ meth
627
+ end
628
+ end
629
+ end
630
+
631
+ ## The top-level entry method into Trollop. Creates a Parser object,
632
+ ## passes the block to it, then parses +args+ with it, handling any
633
+ ## errors or requests for help or version information appropriately (and
634
+ ## then exiting). Modifies +args+ in place. Returns a hash of option
635
+ ## values.
636
+ ##
637
+ ## The block passed in should contain zero or more calls to +opt+
638
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
639
+ ## probably a call to +version+ (Parser#version).
640
+ ##
641
+ ## The returned block contains a value for every option specified with
642
+ ## +opt+. The value will be the value given on the commandline, or the
643
+ ## default value if the option was not specified on the commandline. For
644
+ ## every option specified on the commandline, a key "<option
645
+ ## name>_given" will also be set in the hash.
646
+ ##
647
+ ## Example:
648
+ ##
649
+ ## require 'trollop'
650
+ ## opts = Trollop::options do
651
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
652
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
653
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
654
+ ## opt :num_thumbs, "Number of thumbs", :type => :int # an integer --num-thumbs <i>, defaulting to nil
655
+ ## end
656
+ ##
657
+ ## ## if called with no arguments
658
+ ## p opts # => { :monkey => false, :goat => true, :num_limbs => 4, :num_thumbs => nil }
659
+ ##
660
+ ## ## if called with --monkey
661
+ ## p opts # => {:monkey_given=>true, :monkey=>true, :goat=>true, :num_limbs=>4, :help=>false, :num_thumbs=>nil}
662
+ ##
663
+ ## See more examples at http://trollop.rubyforge.org.
664
+ def options args = ARGV, *a, &b
665
+ @p = Parser.new(*a, &b)
666
+ begin
667
+ vals = @p.parse args
668
+ args.clear
669
+ @p.leftovers.each { |l| args << l }
670
+ vals
671
+ rescue CommandlineError => e
672
+ $stderr.puts "Error: #{e.message}."
673
+ $stderr.puts "Try --help for help."
674
+ exit(-1)
675
+ rescue HelpNeeded
676
+ @p.educate
677
+ exit
678
+ rescue VersionNeeded
679
+ puts @p.version
680
+ exit
681
+ end
682
+ end
683
+
684
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
685
+ ## 'msg', and dies. Example:
686
+ ##
687
+ ## options do
688
+ ## opt :volume, :default => 0.0
689
+ ## end
690
+ ##
691
+ ## die :volume, "too loud" if opts[:volume] > 10.0
692
+ ## die :volume, "too soft" if opts[:volume] < 0.1
693
+ ##
694
+ ## In the one-argument case, simply print that message, a notice
695
+ ## about -h, and die. Example:
696
+ ##
697
+ ## options do
698
+ ## opt :whatever # ...
699
+ ## end
700
+ ##
701
+ ## Trollop::die "need at least one filename" if ARGV.empty?
702
+ def die arg, msg=nil
703
+ if msg
704
+ $stderr.puts "Error: argument --#{@p.specs[arg][:long]} #{msg}."
705
+ else
706
+ $stderr.puts "Error: #{arg}."
707
+ end
708
+ $stderr.puts "Try --help for help."
709
+ exit(-1)
710
+ end
711
+
712
+ module_function :options, :die
713
+
714
+ end # module
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hostgitrb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Raoul Felix
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-04-03 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mg
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: HostGitRb allows you to share your Git repositories with other users using SSH Public keys as authentication. You only need one shell account, which makes this great to use in a shared hosting environment, and users won't be able to do anything else other than push/pull to the repositories you define.
26
+ email: rf@rfelix.com
27
+ executables:
28
+ - hostgitrb
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/only_git.rb
35
+ - vendor/trollop.rb
36
+ - bin/hostgitrb
37
+ - Rakefile
38
+ - README.textile
39
+ has_rdoc: false
40
+ homepage: http://rfelix.com/
41
+ post_install_message:
42
+ rdoc_options: []
43
+
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.3.1
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: Simple Git repository hosting using SSH Public Keys
65
+ test_files: []
66
+