pagy 5.7.5 → 8.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +745 -0
  4. data/apps/demo.ru +435 -0
  5. data/apps/rails.ru +213 -0
  6. data/apps/repro.ru +177 -0
  7. data/apps/tmp/calendar.sqlite3 +0 -0
  8. data/apps/tmp/calendar.sqlite3-shm +0 -0
  9. data/apps/tmp/calendar.sqlite3-wal +0 -0
  10. data/apps/tmp/local_secret.txt +1 -0
  11. data/bin/pagy +100 -0
  12. data/{lib/config → config}/pagy.rb +72 -96
  13. data/javascripts/pagy-module.js +100 -0
  14. data/javascripts/pagy.d.ts +5 -0
  15. data/javascripts/pagy.js +4 -0
  16. data/javascripts/pagy.min.js +4 -0
  17. data/javascripts/pagy.min.js.map +10 -0
  18. data/javascripts/pagy.mjs +100 -0
  19. data/lib/optimist.rb +1022 -0
  20. data/lib/pagy/backend.rb +16 -4
  21. data/lib/pagy/calendar/day.rb +22 -10
  22. data/lib/pagy/calendar/month.rb +35 -9
  23. data/lib/pagy/calendar/quarter.rb +36 -10
  24. data/lib/pagy/calendar/unit.rb +106 -0
  25. data/lib/pagy/calendar/week.rb +16 -16
  26. data/lib/pagy/calendar/year.rb +15 -9
  27. data/lib/pagy/calendar.rb +60 -68
  28. data/lib/pagy/console.rb +3 -3
  29. data/lib/pagy/countless.rb +17 -12
  30. data/lib/pagy/exceptions.rb +1 -1
  31. data/lib/pagy/extras/arel.rb +2 -2
  32. data/lib/pagy/extras/array.rb +2 -2
  33. data/lib/pagy/extras/bootstrap.rb +57 -53
  34. data/lib/pagy/extras/bulma.rb +53 -57
  35. data/lib/pagy/extras/calendar.rb +54 -37
  36. data/lib/pagy/extras/countless.rb +3 -3
  37. data/lib/pagy/extras/elasticsearch_rails.rb +12 -11
  38. data/lib/pagy/extras/foundation.rb +59 -54
  39. data/lib/pagy/extras/gearbox.rb +32 -19
  40. data/lib/pagy/extras/headers.rb +11 -11
  41. data/lib/pagy/extras/i18n.rb +5 -5
  42. data/lib/pagy/extras/items.rb +34 -24
  43. data/lib/pagy/extras/{frontend_helpers.rb → js_tools.rb} +24 -24
  44. data/lib/pagy/extras/jsonapi.rb +79 -0
  45. data/lib/pagy/extras/materialize.rb +63 -47
  46. data/lib/pagy/extras/meilisearch.rb +26 -22
  47. data/lib/pagy/extras/metadata.rb +13 -9
  48. data/lib/pagy/extras/overflow.rb +13 -10
  49. data/lib/pagy/extras/pagy.rb +82 -0
  50. data/lib/pagy/extras/searchkick.rb +12 -11
  51. data/lib/pagy/extras/semantic.rb +57 -45
  52. data/lib/pagy/extras/size.rb +40 -0
  53. data/lib/pagy/extras/standalone.rb +12 -16
  54. data/lib/pagy/extras/trim.rb +13 -13
  55. data/lib/pagy/extras/uikit.rb +60 -46
  56. data/lib/pagy/frontend.rb +62 -45
  57. data/lib/pagy/i18n.rb +4 -3
  58. data/lib/pagy/url_helpers.rb +13 -25
  59. data/lib/pagy.rb +61 -59
  60. data/locales/ar.yml +28 -0
  61. data/locales/be.yml +25 -0
  62. data/locales/bg.yml +21 -0
  63. data/locales/bs.yml +25 -0
  64. data/locales/ca.yml +21 -0
  65. data/locales/ckb.yml +18 -0
  66. data/locales/cs.yml +23 -0
  67. data/locales/da.yml +21 -0
  68. data/locales/de.yml +21 -0
  69. data/locales/en.yml +21 -0
  70. data/locales/es.yml +21 -0
  71. data/locales/fr.yml +21 -0
  72. data/locales/hr.yml +25 -0
  73. data/locales/id.yml +19 -0
  74. data/locales/it.yml +21 -0
  75. data/locales/ja.yml +19 -0
  76. data/locales/km.yml +19 -0
  77. data/locales/ko.yml +17 -0
  78. data/locales/nb.yml +21 -0
  79. data/locales/nl.yml +21 -0
  80. data/locales/nn.yml +21 -0
  81. data/locales/pl.yml +25 -0
  82. data/locales/pt-BR.yml +21 -0
  83. data/locales/pt.yml +21 -0
  84. data/locales/ru.yml +25 -0
  85. data/locales/sr.yml +25 -0
  86. data/locales/sv-SE.yml +21 -0
  87. data/locales/sv.yml +21 -0
  88. data/locales/sw.yml +23 -0
  89. data/locales/ta.yml +23 -0
  90. data/locales/tr.yml +19 -0
  91. data/locales/uk.yml +25 -0
  92. data/locales/vi.yml +17 -0
  93. data/locales/zh-CN.yml +19 -0
  94. data/locales/zh-HK.yml +19 -0
  95. data/locales/zh-TW.yml +19 -0
  96. data/stylesheets/pagy.css +46 -0
  97. data/stylesheets/pagy.scss +48 -0
  98. data/stylesheets/pagy.tailwind.css +21 -0
  99. metadata +79 -66
  100. data/lib/javascripts/pagy-dev.js +0 -118
  101. data/lib/javascripts/pagy-module.d.ts +0 -30
  102. data/lib/javascripts/pagy-module.js +0 -117
  103. data/lib/javascripts/pagy.js +0 -1
  104. data/lib/locales/ar.yml +0 -26
  105. data/lib/locales/bg.yml +0 -22
  106. data/lib/locales/bs.yml +0 -24
  107. data/lib/locales/ca.yml +0 -22
  108. data/lib/locales/cs.yml +0 -22
  109. data/lib/locales/da.yml +0 -22
  110. data/lib/locales/de.yml +0 -22
  111. data/lib/locales/en.yml +0 -22
  112. data/lib/locales/es.yml +0 -22
  113. data/lib/locales/fr.yml +0 -22
  114. data/lib/locales/hr.yml +0 -24
  115. data/lib/locales/id.yml +0 -20
  116. data/lib/locales/it.yml +0 -22
  117. data/lib/locales/ja.yml +0 -20
  118. data/lib/locales/km.yml +0 -19
  119. data/lib/locales/ko.yml +0 -20
  120. data/lib/locales/nb.yml +0 -22
  121. data/lib/locales/nl.yml +0 -22
  122. data/lib/locales/pl.yml +0 -24
  123. data/lib/locales/pt-BR.yml +0 -22
  124. data/lib/locales/pt.yml +0 -22
  125. data/lib/locales/ru.yml +0 -24
  126. data/lib/locales/sr.yml +0 -23
  127. data/lib/locales/sv-SE.yml +0 -23
  128. data/lib/locales/sv.yml +0 -23
  129. data/lib/locales/sw.yml +0 -22
  130. data/lib/locales/ta.yml +0 -22
  131. data/lib/locales/tr.yml +0 -20
  132. data/lib/locales/uk.yml +0 -24
  133. data/lib/locales/zh-CN.yml +0 -20
  134. data/lib/locales/zh-HK.yml +0 -20
  135. data/lib/locales/zh-TW.yml +0 -20
  136. data/lib/pagy/calendar/month_mixin.rb +0 -49
  137. data/lib/pagy/extras/navs.rb +0 -63
  138. data/lib/pagy/extras/support.rb +0 -54
  139. data/lib/templates/bootstrap_nav.html.erb +0 -24
  140. data/lib/templates/bootstrap_nav.html.haml +0 -34
  141. data/lib/templates/bootstrap_nav.html.slim +0 -34
  142. data/lib/templates/bulma_nav.html.erb +0 -24
  143. data/lib/templates/bulma_nav.html.haml +0 -32
  144. data/lib/templates/bulma_nav.html.slim +0 -32
  145. data/lib/templates/foundation_nav.html.erb +0 -24
  146. data/lib/templates/foundation_nav.html.haml +0 -34
  147. data/lib/templates/foundation_nav.html.slim +0 -34
  148. data/lib/templates/nav.html.erb +0 -22
  149. data/lib/templates/nav.html.haml +0 -30
  150. data/lib/templates/nav.html.slim +0 -29
  151. data/lib/templates/uikit_nav.html.erb +0 -15
  152. data/lib/templates/uikit_nav.html.haml +0 -28
  153. data/lib/templates/uikit_nav.html.slim +0 -28
data/lib/optimist.rb ADDED
@@ -0,0 +1,1022 @@
1
+ # lib/optimist.rb -- optimist command-line processing library
2
+ # Copyright (c) 2008-2014 William Morgan.
3
+ # Copyright (c) 2014 Red Hat, Inc.
4
+ # optimist is licensed under the MIT license.
5
+
6
+ require 'date'
7
+
8
+ module Optimist
9
+ VERSION = "3.1.0"
10
+
11
+ ## Thrown by Parser in the event of a commandline error. Not needed if
12
+ ## you're using the Optimist::options entry.
13
+ class CommandlineError < StandardError
14
+ attr_reader :error_code
15
+
16
+ def initialize(msg, error_code = nil)
17
+ super(msg)
18
+ @error_code = error_code
19
+ end
20
+ end
21
+
22
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
23
+ ## automatically by Optimist#options.
24
+ class HelpNeeded < StandardError
25
+ end
26
+
27
+ ## Thrown by Parser if the user passes in '-v' or '--version'. Handled
28
+ ## automatically by Optimist#options.
29
+ class VersionNeeded < StandardError
30
+ end
31
+
32
+ ## Regex for floating point numbers
33
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
34
+
35
+ ## Regex for parameters
36
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
37
+
38
+ ## The commandline parser. In typical usage, the methods in this class
39
+ ## will be handled internally by Optimist::options. In this case, only the
40
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
41
+ ## typically be called.
42
+ ##
43
+ ## If you want to instantiate this class yourself (for more complicated
44
+ ## argument-parsing logic), call #parse to actually produce the output hash,
45
+ ## and consider calling it from within
46
+ ## Optimist::with_standard_exception_handling.
47
+ class Parser
48
+
49
+ ## The registry is a class-instance-variable map of option aliases to their subclassed Option class.
50
+ @registry = {}
51
+
52
+ ## The Option subclasses are responsible for registering themselves using this function.
53
+ def self.register(lookup, klass)
54
+ @registry[lookup.to_sym] = klass
55
+ end
56
+
57
+ ## Gets the class from the registry.
58
+ ## Can be given either a class-name, e.g. Integer, a string, e.g "integer", or a symbol, e.g :integer
59
+ def self.registry_getopttype(type)
60
+ return nil unless type
61
+ if type.respond_to?(:name)
62
+ type = type.name
63
+ lookup = type.downcase.to_sym
64
+ else
65
+ lookup = type.to_sym
66
+ end
67
+ raise ArgumentError, "Unsupported argument type '#{type}', registry lookup '#{lookup}'" unless @registry.has_key?(lookup)
68
+ return @registry[lookup].new
69
+ end
70
+
71
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
72
+
73
+ ## The values from the commandline that were not interpreted by #parse.
74
+ attr_reader :leftovers
75
+
76
+ ## The complete configuration hashes for each option. (Mainly useful
77
+ ## for testing.)
78
+ attr_reader :specs
79
+
80
+ ## A flag that determines whether or not to raise an error if the parser is passed one or more
81
+ ## options that were not registered ahead of time. If 'true', then the parser will simply
82
+ ## ignore options that it does not recognize.
83
+ attr_accessor :ignore_invalid_options
84
+
85
+ ## Initializes the parser, and instance-evaluates any block given.
86
+ def initialize(*a, &b)
87
+ @version = nil
88
+ @leftovers = []
89
+ @specs = {}
90
+ @long = {}
91
+ @short = {}
92
+ @order = []
93
+ @constraints = []
94
+ @stop_words = []
95
+ @stop_on_unknown = false
96
+ @educate_on_error = false
97
+ @synopsis = nil
98
+ @usage = nil
99
+
100
+ # instance_eval(&b) if b # can't take arguments
101
+ cloaker(&b).bind(self).call(*a) if b
102
+ end
103
+
104
+ ## Define an option. +name+ is the option name, a unique identifier
105
+ ## for the option that you will use internally, which should be a
106
+ ## symbol or a string. +desc+ is a string description which will be
107
+ ## displayed in help messages.
108
+ ##
109
+ ## Takes the following optional arguments:
110
+ ##
111
+ ## [+: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.
112
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+. Use :none: to not have a short value.
113
+ ## [+: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.
114
+ ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Optimist::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+.
115
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
116
+ ## [+: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.)
117
+ ##
118
+ ## Note that there are two types of argument multiplicity: an argument
119
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
120
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
121
+ ##
122
+ ## Arguments that take multiple values should have a +:type+ parameter
123
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
124
+ ## value of an array of the correct type (e.g. [String]). The
125
+ ## value of this argument will be an array of the parameters on the
126
+ ## commandline.
127
+ ##
128
+ ## Arguments that can occur multiple times should be marked with
129
+ ## +:multi+ => +true+. The value of this argument will also be an array.
130
+ ## In contrast with regular non-multi options, if not specified on
131
+ ## the commandline, the default value will be [], not nil.
132
+ ##
133
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
134
+ ## +:multi+ => +true+), in which case the value of the argument will be
135
+ ## an array of arrays.
136
+ ##
137
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
138
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
139
+ ## is a multi-value argument as well as a multi-occurrence argument.
140
+ ## In this case, Optimist assumes that it's not a multi-value argument.
141
+ ## If you want a multi-value, multi-occurrence argument with a default
142
+ ## value, you must specify +:type+ as well.
143
+
144
+ def opt(name, desc = "", opts = {}, &b)
145
+ opts[:callback] ||= b if block_given?
146
+ opts[:desc] ||= desc
147
+
148
+ o = Option.create(name, desc, opts)
149
+
150
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? o.name
151
+ raise ArgumentError, "long option name #{o.long.inspect} is already taken; please specify a (different) :long" if @long[o.long]
152
+ raise ArgumentError, "short option name #{o.short.inspect} is already taken; please specify a (different) :short" if @short[o.short]
153
+ @long[o.long] = o.name
154
+ @short[o.short] = o.name if o.short?
155
+ @specs[o.name] = o
156
+ @order << [:opt, o.name]
157
+ end
158
+
159
+ ## Sets the version string. If set, the user can request the version
160
+ ## on the commandline. Should probably be of the form "<program name>
161
+ ## <version number>".
162
+ def version(s = nil)
163
+ s ? @version = s : @version
164
+ end
165
+
166
+ ## Sets the usage string. If set the message will be printed as the
167
+ ## first line in the help (educate) output and ending in two new
168
+ ## lines.
169
+ def usage(s = nil)
170
+ s ? @usage = s : @usage
171
+ end
172
+
173
+ ## Adds a synopsis (command summary description) right below the
174
+ ## usage line, or as the first line if usage isn't specified.
175
+ def synopsis(s = nil)
176
+ s ? @synopsis = s : @synopsis
177
+ end
178
+
179
+ ## Adds text to the help display. Can be interspersed with calls to
180
+ ## #opt to build a multi-section help page.
181
+ def banner(s)
182
+ @order << [:text, s]
183
+ end
184
+ alias_method :text, :banner
185
+
186
+ ## Marks two (or more!) options as requiring each other. Only handles
187
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
188
+ ## better modeled with Optimist::die.
189
+ def depends(*syms)
190
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
191
+ @constraints << [:depends, syms]
192
+ end
193
+
194
+ ## Marks two (or more!) options as conflicting.
195
+ def conflicts(*syms)
196
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
197
+ @constraints << [:conflicts, syms]
198
+ end
199
+
200
+ ## Marks two (or more!) options as required but mutually exclusive.
201
+ def either(*syms)
202
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
203
+ @constraints << [:conflicts, syms]
204
+ @constraints << [:either, syms]
205
+ end
206
+
207
+ ## Defines a set of words which cause parsing to terminate when
208
+ ## encountered, such that any options to the left of the word are
209
+ ## parsed as usual, and options to the right of the word are left
210
+ ## intact.
211
+ ##
212
+ ## A typical use case would be for subcommand support, where these
213
+ ## would be set to the list of subcommands. A subsequent Optimist
214
+ ## invocation would then be used to parse subcommand options, after
215
+ ## shifting the subcommand off of ARGV.
216
+ def stop_on(*words)
217
+ @stop_words = [*words].flatten
218
+ end
219
+
220
+ ## Similar to #stop_on, but stops on any unknown word when encountered
221
+ ## (unless it is a parameter for an argument). This is useful for
222
+ ## cases where you don't know the set of subcommands ahead of time,
223
+ ## i.e., without first parsing the global options.
224
+ def stop_on_unknown
225
+ @stop_on_unknown = true
226
+ end
227
+
228
+ ## Instead of displaying "Try --help for help." on an error
229
+ ## display the usage (via educate)
230
+ def educate_on_error
231
+ @educate_on_error = true
232
+ end
233
+
234
+ ## Parses the commandline. Typically called by Optimist::options,
235
+ ## but you can call it directly if you need more control.
236
+ ##
237
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
238
+ def parse(cmdline = ARGV)
239
+ vals = {}
240
+ required = {}
241
+
242
+ opt :version, "Print version and exit" if @version && ! (@specs[:version] || @long["version"])
243
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
244
+
245
+ @specs.each do |sym, opts|
246
+ required[sym] = true if opts.required?
247
+ vals[sym] = opts.default
248
+ vals[sym] = [] if opts.multi && !opts.default # multi arguments default to [], not nil
249
+ end
250
+
251
+ resolve_default_short_options!
252
+
253
+ ## resolve symbols
254
+ given_args = {}
255
+ @leftovers = each_arg cmdline do |arg, params|
256
+ ## handle --no- forms
257
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
258
+ ["--#{$1}", true]
259
+ else
260
+ [arg, false]
261
+ end
262
+
263
+ sym = case arg
264
+ when /^-([^-])$/ then @short[$1]
265
+ when /^--([^-]\S*)$/ then @long[$1] || @long["no-#{$1}"]
266
+ else raise CommandlineError, "invalid argument syntax: '#{arg}'"
267
+ end
268
+
269
+ sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
270
+
271
+ next nil if ignore_invalid_options && !sym
272
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
273
+
274
+ if given_args.include?(sym) && !@specs[sym].multi?
275
+ raise CommandlineError, "option '#{arg}' specified multiple times"
276
+ end
277
+
278
+ given_args[sym] ||= {}
279
+ given_args[sym][:arg] = arg
280
+ given_args[sym][:negative_given] = negative_given
281
+ given_args[sym][:params] ||= []
282
+
283
+ # The block returns the number of parameters taken.
284
+ num_params_taken = 0
285
+
286
+ unless params.empty?
287
+ if @specs[sym].single_arg?
288
+ given_args[sym][:params] << params[0, 1] # take the first parameter
289
+ num_params_taken = 1
290
+ elsif @specs[sym].multi_arg?
291
+ given_args[sym][:params] << params # take all the parameters
292
+ num_params_taken = params.size
293
+ end
294
+ end
295
+
296
+ num_params_taken
297
+ end
298
+
299
+ ## check for version and help args
300
+ raise VersionNeeded if given_args.include? :version
301
+ raise HelpNeeded if given_args.include? :help
302
+
303
+ ## check constraint satisfaction
304
+ @constraints.each do |type, syms|
305
+ constraint_sym = syms.find { |sym| given_args[sym] }
306
+
307
+ case type
308
+ when :depends
309
+ next unless constraint_sym
310
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} requires --#{@specs[sym].long}" unless given_args.include? sym }
311
+ when :conflicts
312
+ next unless constraint_sym
313
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} conflicts with --#{@specs[sym].long}" if given_args.include?(sym) && (sym != constraint_sym) }
314
+ when :either
315
+ raise CommandlineError, "one of #{syms.map { |sym| "--#{@specs[sym].long}" }.join(', ') } is required" if (syms & given_args.keys).size != 1
316
+ end
317
+ end
318
+
319
+ required.each do |sym, val|
320
+ raise CommandlineError, "option --#{@specs[sym].long} must be specified" unless given_args.include? sym
321
+ end
322
+
323
+ ## parse parameters
324
+ given_args.each do |sym, given_data|
325
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
326
+
327
+ opts = @specs[sym]
328
+ if params.empty? && !opts.flag?
329
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless opts.default
330
+ params << (opts.array_default? ? opts.default.clone : [opts.default])
331
+ end
332
+
333
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
334
+
335
+ vals[sym] = opts.parse(params, negative_given)
336
+
337
+ if opts.single_arg?
338
+ if opts.multi? # multiple options, each with a single parameter
339
+ vals[sym] = vals[sym].map { |p| p[0] }
340
+ else # single parameter
341
+ vals[sym] = vals[sym][0][0]
342
+ end
343
+ elsif opts.multi_arg? && !opts.multi?
344
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
345
+ end
346
+ # else: multiple options, with multiple parameters
347
+
348
+ opts.callback.call(vals[sym]) if opts.callback
349
+ end
350
+
351
+ ## modify input in place with only those
352
+ ## arguments we didn't process
353
+ cmdline.clear
354
+ @leftovers.each { |l| cmdline << l }
355
+
356
+ ## allow openstruct-style accessors
357
+ class << vals
358
+ def method_missing(m, *_args)
359
+ self[m] || self[m.to_s]
360
+ end
361
+ end
362
+ vals
363
+ end
364
+
365
+ ## Print the help message to +stream+.
366
+ def educate(stream = $stdout)
367
+ width # hack: calculate it now; otherwise we have to be careful not to
368
+ # call this unless the cursor's at the beginning of a line.
369
+
370
+ left = {}
371
+ @specs.each { |name, spec| left[name] = spec.educate }
372
+
373
+ leftcol_width = left.values.map(&:length).max || 0
374
+ rightcol_start = leftcol_width + 6 # spaces
375
+
376
+ unless @order.size > 0 && @order.first.first == :text
377
+ command_name = File.basename($0).gsub(/\.[^.]+$/, '')
378
+ stream.puts "Usage: #{command_name} #{@usage}\n" if @usage
379
+ stream.puts "#{@synopsis}\n" if @synopsis
380
+ stream.puts if @usage || @synopsis
381
+ stream.puts "#{@version}\n" if @version
382
+ stream.puts "Options:"
383
+ end
384
+
385
+ @order.each do |what, opt|
386
+ if what == :text
387
+ stream.puts wrap(opt)
388
+ next
389
+ end
390
+
391
+ spec = @specs[opt]
392
+ stream.printf " %-#{leftcol_width}s ", left[opt]
393
+ desc = spec.description_with_default
394
+
395
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
396
+ end
397
+ end
398
+
399
+ def width #:nodoc:
400
+ @width ||= if $stdout.tty?
401
+ begin
402
+ require 'io/console'
403
+ w = IO.console.winsize.last
404
+ w.to_i > 0 ? w : 80
405
+ rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
406
+ legacy_width
407
+ end
408
+ else
409
+ 80
410
+ end
411
+ end
412
+
413
+ def legacy_width
414
+ # Support for older Rubies where io/console is not available
415
+ `tput cols`.to_i
416
+ rescue Errno::ENOENT
417
+ 80
418
+ end
419
+ private :legacy_width
420
+
421
+ def wrap(str, opts = {}) # :nodoc:
422
+ if str == ""
423
+ [""]
424
+ else
425
+ inner = false
426
+ str.split("\n").map do |s|
427
+ line = wrap_line s, opts.merge(:inner => inner)
428
+ inner = true
429
+ line
430
+ end.flatten
431
+ end
432
+ end
433
+
434
+ ## The per-parser version of Optimist::die (see that for documentation).
435
+ def die(arg, msg = nil, error_code = nil)
436
+ msg, error_code = nil, msg if msg.kind_of?(Integer)
437
+ if msg
438
+ $stderr.puts "Error: argument --#{@specs[arg].long} #{msg}."
439
+ else
440
+ $stderr.puts "Error: #{arg}."
441
+ end
442
+ if @educate_on_error
443
+ $stderr.puts
444
+ educate $stderr
445
+ else
446
+ $stderr.puts "Try --help for help."
447
+ end
448
+ exit(error_code || -1)
449
+ end
450
+
451
+ private
452
+
453
+ ## yield successive arg, parameter pairs
454
+ def each_arg(args)
455
+ remains = []
456
+ i = 0
457
+
458
+ until i >= args.length
459
+ return remains += args[i..-1] if @stop_words.member? args[i]
460
+ case args[i]
461
+ when /^--$/ # arg terminator
462
+ return remains += args[(i + 1)..-1]
463
+ when /^--(\S+?)=(.*)$/ # long argument with equals
464
+ num_params_taken = yield "--#{$1}", [$2]
465
+ if num_params_taken.nil?
466
+ remains << args[i]
467
+ if @stop_on_unknown
468
+ return remains += args[i + 1..-1]
469
+ end
470
+ end
471
+ i += 1
472
+ when /^--(\S+)$/ # long argument
473
+ params = collect_argument_parameters(args, i + 1)
474
+ num_params_taken = yield args[i], params
475
+
476
+ if num_params_taken.nil?
477
+ remains << args[i]
478
+ if @stop_on_unknown
479
+ return remains += args[i + 1..-1]
480
+ end
481
+ else
482
+ i += num_params_taken
483
+ end
484
+ i += 1
485
+ when /^-(\S+)$/ # one or more short arguments
486
+ short_remaining = ""
487
+ shortargs = $1.split(//)
488
+ shortargs.each_with_index do |a, j|
489
+ if j == (shortargs.length - 1)
490
+ params = collect_argument_parameters(args, i + 1)
491
+
492
+ num_params_taken = yield "-#{a}", params
493
+ unless num_params_taken
494
+ short_remaining << a
495
+ if @stop_on_unknown
496
+ remains << "-#{short_remaining}"
497
+ return remains += args[i + 1..-1]
498
+ end
499
+ else
500
+ i += num_params_taken
501
+ end
502
+ else
503
+ unless yield "-#{a}", []
504
+ short_remaining << a
505
+ if @stop_on_unknown
506
+ short_remaining += shortargs[j + 1..-1].join
507
+ remains << "-#{short_remaining}"
508
+ return remains += args[i + 1..-1]
509
+ end
510
+ end
511
+ end
512
+ end
513
+
514
+ unless short_remaining.empty?
515
+ remains << "-#{short_remaining}"
516
+ end
517
+ i += 1
518
+ else
519
+ if @stop_on_unknown
520
+ return remains += args[i..-1]
521
+ else
522
+ remains << args[i]
523
+ i += 1
524
+ end
525
+ end
526
+ end
527
+
528
+ remains
529
+ end
530
+
531
+ def collect_argument_parameters(args, start_at)
532
+ params = []
533
+ pos = start_at
534
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
535
+ params << args[pos]
536
+ pos += 1
537
+ end
538
+ params
539
+ end
540
+
541
+ def resolve_default_short_options!
542
+ @order.each do |type, name|
543
+ opts = @specs[name]
544
+ next if type != :opt || opts.short
545
+
546
+ c = opts.long.split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
547
+ if c # found a character to use
548
+ opts.short = c
549
+ @short[c] = name
550
+ end
551
+ end
552
+ end
553
+
554
+ def wrap_line(str, opts = {})
555
+ prefix = opts[:prefix] || 0
556
+ width = opts[:width] || (self.width - 1)
557
+ start = 0
558
+ ret = []
559
+ until start > str.length
560
+ nextt =
561
+ if start + width >= str.length
562
+ str.length
563
+ else
564
+ x = str.rindex(/\s/, start + width)
565
+ x = str.index(/\s/, start) if x && x < start
566
+ x || str.length
567
+ end
568
+ ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start...nextt]
569
+ start = nextt + 1
570
+ end
571
+ ret
572
+ end
573
+
574
+ ## instance_eval but with ability to handle block arguments
575
+ ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
576
+ def cloaker(&b)
577
+ (class << self; self; end).class_eval do
578
+ define_method :cloaker_, &b
579
+ meth = instance_method :cloaker_
580
+ remove_method :cloaker_
581
+ meth
582
+ end
583
+ end
584
+ end
585
+
586
+ class Option
587
+
588
+ attr_accessor :name, :short, :long, :default
589
+ attr_writer :multi_given
590
+
591
+ def initialize
592
+ @long = nil
593
+ @short = nil
594
+ @name = nil
595
+ @multi_given = false
596
+ @hidden = false
597
+ @default = nil
598
+ @optshash = Hash.new()
599
+ end
600
+
601
+ def opts(key)
602
+ @optshash[key]
603
+ end
604
+
605
+ def opts=(o)
606
+ @optshash = o
607
+ end
608
+
609
+ ## Indicates a flag option, which is an option without an argument
610
+ def flag? ; false ; end
611
+ def single_arg?
612
+ !self.multi_arg? && !self.flag?
613
+ end
614
+
615
+ def multi ; @multi_given ; end
616
+ alias multi? multi
617
+
618
+ ## Indicates that this is a multivalued (Array type) argument
619
+ def multi_arg? ; false ; end
620
+ ## note: Option-Types with both multi_arg? and flag? false are single-parameter (normal) options.
621
+
622
+ def array_default? ; self.default.kind_of?(Array) ; end
623
+
624
+ def short? ; short && short != :none ; end
625
+
626
+ def callback ; opts(:callback) ; end
627
+ def desc ; opts(:desc) ; end
628
+
629
+ def required? ; opts(:required) ; end
630
+
631
+ def parse(_paramlist, _neg_given)
632
+ raise NotImplementedError, "parse must be overridden for newly registered type"
633
+ end
634
+
635
+ # provide type-format string. default to empty, but user should probably override it
636
+ def type_format ; "" ; end
637
+
638
+ def educate
639
+ (short? ? "-#{short}, " : "") + "--#{long}" + type_format + (flag? && default ? ", --no-#{long}" : "")
640
+ end
641
+
642
+ ## Format the educate-line description including the default-value(s)
643
+ def description_with_default
644
+ return desc unless default
645
+ default_s = case default
646
+ when $stdout then '<stdout>'
647
+ when $stdin then '<stdin>'
648
+ when $stderr then '<stderr>'
649
+ when Array
650
+ default.join(', ')
651
+ else
652
+ default.to_s
653
+ end
654
+ defword = desc.end_with?('.') ? 'Default' : 'default'
655
+ return "#{desc} (#{defword}: #{default_s})"
656
+ end
657
+
658
+ ## Provide a way to register symbol aliases to the Parser
659
+ def self.register_alias(*alias_keys)
660
+ alias_keys.each do |alias_key|
661
+ # pass in the alias-key and the class
662
+ Parser.register(alias_key, self)
663
+ end
664
+ end
665
+
666
+ ## Factory class methods ...
667
+
668
+ # Determines which type of object to create based on arguments passed
669
+ # to +Optimist::opt+. This is trickier in Optimist, than other cmdline
670
+ # parsers (e.g. Slop) because we allow the +default:+ to be able to
671
+ # set the option's type.
672
+ def self.create(name, desc="", opts={}, settings={})
673
+
674
+ opttype = Optimist::Parser.registry_getopttype(opts[:type])
675
+ opttype_from_default = get_klass_from_default(opts, opttype)
676
+
677
+ raise ArgumentError, ":type specification and default type don't match (default type is #{opttype_from_default.class})" if opttype && opttype_from_default && (opttype.class != opttype_from_default.class)
678
+
679
+ opt_inst = (opttype || opttype_from_default || Optimist::BooleanOption.new)
680
+
681
+ ## fill in :long
682
+ opt_inst.long = handle_long_opt(opts[:long], name)
683
+
684
+ ## fill in :short
685
+ opt_inst.short = handle_short_opt(opts[:short])
686
+
687
+ ## fill in :multi
688
+ multi_given = opts[:multi] || false
689
+ opt_inst.multi_given = multi_given
690
+
691
+ ## fill in :default for flags
692
+ defvalue = opts[:default] || opt_inst.default
693
+
694
+ ## autobox :default for :multi (multi-occurrence) arguments
695
+ defvalue = [defvalue] if defvalue && multi_given && !defvalue.kind_of?(Array)
696
+ opt_inst.default = defvalue
697
+ opt_inst.name = name
698
+ opt_inst.opts = opts
699
+ opt_inst
700
+ end
701
+
702
+ private
703
+
704
+ def self.get_type_from_disdef(optdef, opttype, disambiguated_default)
705
+ if disambiguated_default.is_a? Array
706
+ return(optdef.first.class.name.downcase + "s") if !optdef.empty?
707
+ if opttype
708
+ raise ArgumentError, "multiple argument type must be plural" unless opttype.multi_arg?
709
+ return nil
710
+ else
711
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array"
712
+ end
713
+ end
714
+ return disambiguated_default.class.name.downcase
715
+ end
716
+
717
+ def self.get_klass_from_default(opts, opttype)
718
+ ## for options with :multi => true, an array default doesn't imply
719
+ ## a multi-valued argument. for that you have to specify a :type
720
+ ## as well. (this is how we disambiguate an ambiguous situation;
721
+ ## see the docs for Parser#opt for details.)
722
+
723
+ disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && opttype.nil?
724
+ opts[:default].first
725
+ else
726
+ opts[:default]
727
+ end
728
+
729
+ return nil if disambiguated_default.nil?
730
+ type_from_default = get_type_from_disdef(opts[:default], opttype, disambiguated_default)
731
+ return Optimist::Parser.registry_getopttype(type_from_default)
732
+ end
733
+
734
+ def self.handle_long_opt(lopt, name)
735
+ lopt = lopt ? lopt.to_s : name.to_s.gsub("_", "-")
736
+ lopt = case lopt
737
+ when /^--([^-].*)$/ then $1
738
+ when /^[^-]/ then lopt
739
+ else raise ArgumentError, "invalid long option name #{lopt.inspect}"
740
+ end
741
+ end
742
+
743
+ def self.handle_short_opt(sopt)
744
+ sopt = sopt.to_s if sopt && sopt != :none
745
+ sopt = case sopt
746
+ when /^-(.)$/ then $1
747
+ when nil, :none, /^.$/ then sopt
748
+ else raise ArgumentError, "invalid short option name '#{sopt.inspect}'"
749
+ end
750
+
751
+ if sopt
752
+ raise ArgumentError, "a short option name can't be a number or a dash" if sopt =~ ::Optimist::Parser::INVALID_SHORT_ARG_REGEX
753
+ end
754
+ return sopt
755
+ end
756
+
757
+ end
758
+
759
+ # Flag option. Has no arguments. Can be negated with "no-".
760
+ class BooleanOption < Option
761
+ register_alias :flag, :bool, :boolean, :trueclass, :falseclass
762
+ def initialize
763
+ super()
764
+ @default = false
765
+ end
766
+ def flag? ; true ; end
767
+ def parse(_paramlist, neg_given)
768
+ return(self.name.to_s =~ /^no_/ ? neg_given : !neg_given)
769
+ end
770
+ end
771
+
772
+ # Floating point number option class.
773
+ class FloatOption < Option
774
+ register_alias :float, :double
775
+ def type_format ; "=<f>" ; end
776
+ def parse(paramlist, _neg_given)
777
+ paramlist.map do |pg|
778
+ pg.map do |param|
779
+ raise CommandlineError, "option '#{self.name}' needs a floating-point number" unless param.is_a?(Numeric) || param =~ FLOAT_RE
780
+ param.to_f
781
+ end
782
+ end
783
+ end
784
+ end
785
+
786
+ # Integer number option class.
787
+ class IntegerOption < Option
788
+ register_alias :int, :integer, :fixnum
789
+ def type_format ; "=<i>" ; end
790
+ def parse(paramlist, _neg_given)
791
+ paramlist.map do |pg|
792
+ pg.map do |param|
793
+ raise CommandlineError, "option '#{self.name}' needs an integer" unless param.is_a?(Numeric) || param =~ /^-?[\d_]+$/
794
+ param.to_i
795
+ end
796
+ end
797
+ end
798
+ end
799
+
800
+ # Option class for handling IO objects and URLs.
801
+ # Note that this will return the file-handle, not the file-name
802
+ # in the case of file-paths given to it.
803
+ class IOOption < Option
804
+ register_alias :io
805
+ def type_format ; "=<filename/uri>" ; end
806
+ def parse(paramlist, _neg_given)
807
+ paramlist.map do |pg|
808
+ pg.map do |param|
809
+ if param =~ /^(stdin|-)$/i
810
+ $stdin
811
+ else
812
+ require 'open-uri'
813
+ begin
814
+ open param
815
+ rescue SystemCallError => e
816
+ raise CommandlineError, "file or url for option '#{self.name}' cannot be opened: #{e.message}"
817
+ end
818
+ end
819
+ end
820
+ end
821
+ end
822
+ end
823
+
824
+ # Option class for handling Strings.
825
+ class StringOption < Option
826
+ register_alias :string
827
+ def type_format ; "=<s>" ; end
828
+ def parse(paramlist, _neg_given)
829
+ paramlist.map { |pg| pg.map(&:to_s) }
830
+ end
831
+ end
832
+
833
+ # Option for dates. Uses Chronic if it exists.
834
+ class DateOption < Option
835
+ register_alias :date
836
+ def type_format ; "=<date>" ; end
837
+ def parse(paramlist, _neg_given)
838
+ paramlist.map do |pg|
839
+ pg.map do |param|
840
+ next param if param.is_a?(Date)
841
+ begin
842
+ begin
843
+ require 'chronic'
844
+ time = Chronic.parse(param)
845
+ rescue LoadError
846
+ # chronic is not available
847
+ end
848
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
849
+ rescue ArgumentError
850
+ raise CommandlineError, "option '#{self.name}' needs a date"
851
+ end
852
+ end
853
+ end
854
+ end
855
+ end
856
+
857
+ ### MULTI_OPT_TYPES :
858
+ ## The set of values that indicate a multiple-parameter option (i.e., that
859
+ ## takes multiple space-separated values on the commandline) when passed as
860
+ ## the +:type+ parameter of #opt.
861
+
862
+ # Option class for handling multiple Integers
863
+ class IntegerArrayOption < IntegerOption
864
+ register_alias :fixnums, :ints, :integers
865
+ def type_format ; "=<i+>" ; end
866
+ def multi_arg? ; true ; end
867
+ end
868
+
869
+ # Option class for handling multiple Floats
870
+ class FloatArrayOption < FloatOption
871
+ register_alias :doubles, :floats
872
+ def type_format ; "=<f+>" ; end
873
+ def multi_arg? ; true ; end
874
+ end
875
+
876
+ # Option class for handling multiple Strings
877
+ class StringArrayOption < StringOption
878
+ register_alias :strings
879
+ def type_format ; "=<s+>" ; end
880
+ def multi_arg? ; true ; end
881
+ end
882
+
883
+ # Option class for handling multiple dates
884
+ class DateArrayOption < DateOption
885
+ register_alias :dates
886
+ def type_format ; "=<date+>" ; end
887
+ def multi_arg? ; true ; end
888
+ end
889
+
890
+ # Option class for handling Files/URLs via 'open'
891
+ class IOArrayOption < IOOption
892
+ register_alias :ios
893
+ def type_format ; "=<filename/uri+>" ; end
894
+ def multi_arg? ; true ; end
895
+ end
896
+
897
+ ## The easy, syntactic-sugary entry method into Optimist. Creates a Parser,
898
+ ## passes the block to it, then parses +args+ with it, handling any errors or
899
+ ## requests for help or version information appropriately (and then exiting).
900
+ ## Modifies +args+ in place. Returns a hash of option values.
901
+ ##
902
+ ## The block passed in should contain zero or more calls to +opt+
903
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
904
+ ## probably a call to +version+ (Parser#version).
905
+ ##
906
+ ## The returned block contains a value for every option specified with
907
+ ## +opt+. The value will be the value given on the commandline, or the
908
+ ## default value if the option was not specified on the commandline. For
909
+ ## every option specified on the commandline, a key "<option
910
+ ## name>_given" will also be set in the hash.
911
+ ##
912
+ ## Example:
913
+ ##
914
+ ## require 'optimist'
915
+ ## opts = Optimist::options do
916
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
917
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
918
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
919
+ ## end
920
+ ##
921
+ ## ## if called with no arguments
922
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
923
+ ##
924
+ ## ## if called with --monkey
925
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
926
+ ##
927
+ ## See more examples at http://optimist.rubyforge.org.
928
+ def options(args = ARGV, *a, &b)
929
+ @last_parser = Parser.new(*a, &b)
930
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
931
+ end
932
+
933
+ ## If Optimist::options doesn't do quite what you want, you can create a Parser
934
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
935
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
936
+ ## have these handled for you in the standard manner (e.g. show the help
937
+ ## and then exit upon an HelpNeeded exception), call your code from within
938
+ ## a block passed to this method.
939
+ ##
940
+ ## Note that this method will call System#exit after handling an exception!
941
+ ##
942
+ ## Usage example:
943
+ ##
944
+ ## require 'optimist'
945
+ ## p = Optimist::Parser.new do
946
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
947
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
948
+ ## end
949
+ ##
950
+ ## opts = Optimist::with_standard_exception_handling p do
951
+ ## o = p.parse ARGV
952
+ ## raise Optimist::HelpNeeded if ARGV.empty? # show help screen
953
+ ## o
954
+ ## end
955
+ ##
956
+ ## Requires passing in the parser object.
957
+
958
+ def with_standard_exception_handling(parser)
959
+ yield
960
+ rescue CommandlineError => e
961
+ parser.die(e.message, nil, e.error_code)
962
+ rescue HelpNeeded
963
+ parser.educate
964
+ exit
965
+ rescue VersionNeeded
966
+ puts parser.version
967
+ exit
968
+ end
969
+
970
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
971
+ ## 'msg', and dies. Example:
972
+ ##
973
+ ## options do
974
+ ## opt :volume, :default => 0.0
975
+ ## end
976
+ ##
977
+ ## die :volume, "too loud" if opts[:volume] > 10.0
978
+ ## die :volume, "too soft" if opts[:volume] < 0.1
979
+ ##
980
+ ## In the one-argument case, simply print that message, a notice
981
+ ## about -h, and die. Example:
982
+ ##
983
+ ## options do
984
+ ## opt :whatever # ...
985
+ ## end
986
+ ##
987
+ ## Optimist::die "need at least one filename" if ARGV.empty?
988
+ ##
989
+ ## An exit code can be provide if needed
990
+ ##
991
+ ## Optimist::die "need at least one filename", -2 if ARGV.empty?
992
+ def die(arg, msg = nil, error_code = nil)
993
+ if @last_parser
994
+ @last_parser.die arg, msg, error_code
995
+ else
996
+ raise ArgumentError, "Optimist::die can only be called after Optimist::options"
997
+ end
998
+ end
999
+
1000
+ ## Displays the help message and dies. Example:
1001
+ ##
1002
+ ## options do
1003
+ ## opt :volume, :default => 0.0
1004
+ ## banner <<-EOS
1005
+ ## Usage:
1006
+ ## #$0 [options] <name>
1007
+ ## where [options] are:
1008
+ ## EOS
1009
+ ## end
1010
+ ##
1011
+ ## Optimist::educate if ARGV.empty?
1012
+ def educate
1013
+ if @last_parser
1014
+ @last_parser.educate
1015
+ exit
1016
+ else
1017
+ raise ArgumentError, "Optimist::educate can only be called after Optimist::options"
1018
+ end
1019
+ end
1020
+
1021
+ module_function :options, :die, :educate, :with_standard_exception_handling
1022
+ end # module