darksky-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2de496ac9a489ecdb83e761649f6540bba2f94f3
4
+ data.tar.gz: aaf3e305a49d24c740c4ad9a6114b25cbd96b214
5
+ SHA512:
6
+ metadata.gz: c045f53b13b0e9c514557df793c13b5f148910bb544969178373ff3cfe3d8246d1beaa7094a62ff83fc7e74b024de5efafb6b70b20ac4673a0e76179f6468de5
7
+ data.tar.gz: bf159e9875b718220b04094347ec0a3c6a964d6368f5c9cf6e622bb081d61ed4962e1d9077ca01d08d18609b03063893625b7feefdbb5e8af08f21a162807d13
data/bin/darksky ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ require 'darksky-ruby/trollop'
3
+ require 'darksky-ruby'
4
+
5
+
6
+ opts = Trollop::options do
7
+ banner "darksky [options] <LAT,LON>"
8
+ opt :key, 'API secret key', type: :string
9
+ opt :loc, 'Location (latitude,longtitude)', type: :string
10
+ opt :log, 'Log file', type: :string
11
+ opt :time, 'Timestamp for Time Machine request', type: :string
12
+ opt :verbose, 'Verbose mode'
13
+ end
14
+
15
+ if opts[:log_given]
16
+ Darksky.logger = Logger.new(opts[:log])
17
+ Darksky.logger.level = Logger::WARN
18
+ end
19
+
20
+ if opts[:verbose]
21
+ Darksky.logger = Logger.new(STDOUT) unless opts[:log_given]
22
+ Darksky.logger.level = Logger::DEBUG
23
+ end
24
+ log = Darksky.logger
25
+
26
+ log.debug("Command line arguments: #{opts}")
27
+
28
+ loc = opts[:loc]
29
+ loc ||= ARGV.shift
30
+
31
+ Trollop::die :loc, "is missing" if loc.nil?
32
+
33
+ api = Darksky::API.new(key: opts[:key])
34
+ api.blocks = {minutely: false, hourly: false, daily: false, alerts: false, flags: false}
35
+
36
+ if opts[:time_given]
37
+ data = api.timemachine(loc: loc, ts: opts[:time])
38
+ else
39
+ data = api.forecast(loc: loc)
40
+ end
41
+
42
+ require 'pp'
43
+ pp data if data
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+ require 'darksky-ruby/version'
3
+
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'darksky-ruby'
7
+ s.version = Darksky::Version
8
+ s.authors = ['Ken J.']
9
+ s.email = ['kenjij@gmail.com']
10
+ s.summary = %q{Pure simple Ruby based Darksky REST library}
11
+ s.description = %q{Darksky library written in pure Ruby without external dependancy.}
12
+ s.homepage = 'https://github.com/kenjij/darksky-ruby'
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split($/)
16
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+
19
+ s.required_ruby_version = '>= 2.0'
20
+ end
@@ -0,0 +1,77 @@
1
+ require 'json'
2
+
3
+ module Darksky
4
+
5
+ class API
6
+
7
+ DARKSKY_URL = 'https://api.darksky.net/'
8
+ DARKSKY_PATH_TEMPLATE = '/forecast/%{key}/%{loc}'
9
+ DARKSKY_BLOCK_NAMES = [:currently, :minutely, :hourly, :daily, :alerts, :flags]
10
+
11
+ attr_accessor :key, :latitude, :longitude, :location, :time, :options
12
+
13
+ def initialize(key:, options: {})
14
+ @key = key
15
+ @options = options
16
+ end
17
+
18
+ def forecast(lat: @latitude, lon: @longitude, loc: @location, ts: @time)
19
+ loc = "#{lat},#{lon}" if lat && lon
20
+ raise ArgumentError, 'No location given to forecast' if loc.nil?
21
+ ts = ts.to_i if ts.class == Time
22
+ request(loc, ts)
23
+ end
24
+
25
+ def timemachine(lat: @latitude, lon: @longitude, loc: @location, ts:)
26
+ forecast(lat: lat, lon: lon, loc: loc, ts: ts)
27
+ end
28
+
29
+ def blocks()
30
+ ex = options[:exclude]
31
+ ex.nil? ? ex = [] : ex = ex.split(',').map{ |n| n.to_sym }
32
+ h = {}
33
+ DARKSKY_BLOCK_NAMES.each { |n| h[n] = !ex.include?(n) }
34
+ return h
35
+ end
36
+
37
+ def blocks=(h)
38
+ ex = DARKSKY_BLOCK_NAMES.select { |n| h[n] == false }
39
+ options[:exclude] = ex.join(',')
40
+ end
41
+
42
+ def param(key, val = nil)
43
+ end
44
+
45
+ private
46
+
47
+ def request(loc, ts)
48
+ path = DARKSKY_PATH_TEMPLATE % {key: key, loc: loc}
49
+ path += ",#{ts}" if ts
50
+ options.empty? ? o = nil : o = options
51
+ data = http.get(path: path, params: o)
52
+ return handle_response_data(data)
53
+ end
54
+
55
+ def http
56
+ unless @http
57
+ @http = HTTP.new(DARKSKY_URL)
58
+ end
59
+ return @http
60
+ end
61
+
62
+ def format_path(path)
63
+ path = '/' + path unless path.start_with?('/')
64
+ return path + '.json'
65
+ end
66
+
67
+ def handle_response_data(data)
68
+ if data[:code] != 200
69
+ Darksky.logger.error("HTTP response error: #{data[:code]}\n#{data[:message]}")
70
+ return nil
71
+ end
72
+ return JSON.parse(data[:body], {symbolize_names: true})
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,123 @@
1
+ require 'net/http'
2
+ require 'openssl'
3
+
4
+
5
+ module Darksky
6
+
7
+ class HTTP
8
+
9
+ METHOD_HTTP_CLASS = {
10
+ get: Net::HTTP::Get
11
+ }
12
+
13
+ def self.get(url, params)
14
+ h = HTTP.new(url)
15
+ data = h.get(params: params)
16
+ h.close
17
+ return data
18
+ end
19
+
20
+ attr_reader :init_uri, :http
21
+ attr_accessor :headers
22
+
23
+ def initialize(url, hdrs = nil)
24
+ @init_uri = URI(url)
25
+ raise ArgumentError, 'Invalid URL' unless @init_uri.class <= URI::HTTP
26
+ @http = Net::HTTP.new(init_uri.host, init_uri.port)
27
+ http.use_ssl = init_uri.scheme == 'https'
28
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
29
+ self.headers = hdrs
30
+ end
31
+
32
+ def get(path: nil, params: nil, query: nil)
33
+ return operate(__method__, path: path, params: params, query: query)
34
+ end
35
+
36
+ def close
37
+ http.finish if http.started?
38
+ end
39
+
40
+ private
41
+
42
+ def operate(method, path: nil, params: nil, body: nil, query: nil)
43
+ uri = uri_with_path(path)
44
+ if params
45
+ query = URI.encode_www_form(params)
46
+ Darksky.logger.info('Created urlencoded query from params')
47
+ end
48
+ uri.query = query
49
+ req = METHOD_HTTP_CLASS[method].new(uri)
50
+ data = send(req)
51
+ data = redirect(method, uri, params: params, body: body, query: query) if data.class <= URI::HTTP
52
+ return data
53
+ end
54
+
55
+ def uri_with_path(path)
56
+ uri = init_uri.clone
57
+ uri.path = path unless path.nil?
58
+ return uri
59
+ end
60
+
61
+ def send(req)
62
+ inject_headers_to(req)
63
+ unless http.started?
64
+ Darksky.logger.info('HTTP session not started; starting now')
65
+ http.start
66
+ Darksky.logger.debug("Opened connection to #{http.address}:#{http.port}")
67
+ end
68
+ Darksky.logger.debug("Sending HTTP #{req.method} request to #{req.path}")
69
+ Darksky.logger.debug("Body size: #{req.body.length}") if req.request_body_permitted?
70
+ res = http.request(req)
71
+ return handle_response(res)
72
+ end
73
+
74
+ def inject_headers_to(req)
75
+ return if headers.nil?
76
+ headers.each do |k, v|
77
+ req[k] = v
78
+ end
79
+ Darksky.logger.info('Header injected into HTTP request header')
80
+ end
81
+
82
+ def handle_response(res)
83
+ if res.connection_close?
84
+ Darksky.logger.info('HTTP response header says connection close; closing session now')
85
+ close
86
+ end
87
+ case res
88
+ when Net::HTTPRedirection
89
+ Darksky.logger.info('HTTP response was a redirect')
90
+ data = URI(res['Location'])
91
+ if data.class == URI::Generic
92
+ data = uri_with_path(res['Location'])
93
+ Darksky.logger.debug("Full URI object built for local redirect with path: #{data.path}")
94
+ end
95
+ # when Net::HTTPSuccess
96
+ # when Net::HTTPClientError
97
+ # when Net::HTTPServerError
98
+ else
99
+ data = {
100
+ code: res.code.to_i,
101
+ headers: res.to_hash,
102
+ body: res.body,
103
+ message: res.msg
104
+ }
105
+ end
106
+ return data
107
+ end
108
+
109
+ def redirect(method, uri, params: nil, body: nil, query: nil)
110
+ if uri.host == init_uri.host && uri.port == init_uri.port
111
+ Darksky.logger.info("Local #{method.upcase} redirect, reusing HTTP session")
112
+ new_http = http
113
+ else
114
+ Darksky.logger.info("External #{method.upcase} redirect, spawning new HTTP object")
115
+ new_http = HTTP.new("#{uri.scheme}://#{uri.host}#{uri.path}", headers)
116
+ end
117
+ data = operate(method, uri, params: params, query: query)
118
+ return data
119
+ end
120
+
121
+ end
122
+
123
+ end
@@ -0,0 +1,24 @@
1
+ require 'logger'
2
+
3
+
4
+ module Darksky
5
+
6
+ def self.logger=(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ def self.logger
11
+ @logger ||= NullLogger.new()
12
+ end
13
+
14
+ class NullLogger < Logger
15
+
16
+ def initialize(*args)
17
+ end
18
+
19
+ def add(*args, &block)
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,911 @@
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 MIT license.
5
+
6
+ require 'date'
7
+
8
+ module Trollop
9
+ # note: this is duplicated in gemspec
10
+ # please change over there too
11
+ VERSION = "2.1.2"
12
+
13
+ ## Thrown by Parser in the event of a commandline error. Not needed if
14
+ ## you're using the Trollop::options entry.
15
+ class CommandlineError < StandardError
16
+ attr_reader :error_code
17
+
18
+ def initialize(msg, error_code = nil)
19
+ super(msg)
20
+ @error_code = error_code
21
+ end
22
+ end
23
+
24
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
25
+ ## automatically by Trollop#options.
26
+ class HelpNeeded < StandardError
27
+ end
28
+
29
+ ## Thrown by Parser if the user passes in '-v' or '--version'. Handled
30
+ ## automatically by Trollop#options.
31
+ class VersionNeeded < StandardError
32
+ end
33
+
34
+ ## Regex for floating point numbers
35
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
36
+
37
+ ## Regex for parameters
38
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
39
+
40
+ ## The commandline parser. In typical usage, the methods in this class
41
+ ## will be handled internally by Trollop::options. In this case, only the
42
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
43
+ ## typically be called.
44
+ ##
45
+ ## If you want to instantiate this class yourself (for more complicated
46
+ ## argument-parsing logic), call #parse to actually produce the output hash,
47
+ ## and consider calling it from within
48
+ ## Trollop::with_standard_exception_handling.
49
+ class Parser
50
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
51
+
52
+ ## The values from the commandline that were not interpreted by #parse.
53
+ attr_reader :leftovers
54
+
55
+ ## The complete configuration hashes for each option. (Mainly useful
56
+ ## for testing.)
57
+ attr_reader :specs
58
+
59
+ ## A flag that determines whether or not to raise an error if the parser is passed one or more
60
+ ## options that were not registered ahead of time. If 'true', then the parser will simply
61
+ ## ignore options that it does not recognize.
62
+ attr_accessor :ignore_invalid_options
63
+
64
+ ## Initializes the parser, and instance-evaluates any block given.
65
+ def initialize(*a, &b)
66
+ @version = nil
67
+ @leftovers = []
68
+ @specs = {}
69
+ @long = {}
70
+ @short = {}
71
+ @order = []
72
+ @constraints = []
73
+ @stop_words = []
74
+ @stop_on_unknown = false
75
+ @educate_on_error = false
76
+ @synopsis = nil
77
+ @usage = nil
78
+
79
+ # instance_eval(&b) if b # can't take arguments
80
+ cloaker(&b).bind(self).call(*a) if b
81
+ end
82
+
83
+ ## Define an option. +name+ is the option name, a unique identifier
84
+ ## for the option that you will use internally, which should be a
85
+ ## symbol or a string. +desc+ is a string description which will be
86
+ ## displayed in help messages.
87
+ ##
88
+ ## Takes the following optional arguments:
89
+ ##
90
+ ## [+: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.
91
+ ## [+: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.
92
+ ## [+: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.
93
+ ## [+: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+.
94
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
95
+ ## [+: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.)
96
+ ##
97
+ ## Note that there are two types of argument multiplicity: an argument
98
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
99
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
100
+ ##
101
+ ## Arguments that take multiple values should have a +:type+ parameter
102
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
103
+ ## value of an array of the correct type (e.g. [String]). The
104
+ ## value of this argument will be an array of the parameters on the
105
+ ## commandline.
106
+ ##
107
+ ## Arguments that can occur multiple times should be marked with
108
+ ## +:multi+ => +true+. The value of this argument will also be an array.
109
+ ## In contrast with regular non-multi options, if not specified on
110
+ ## the commandline, the default value will be [], not nil.
111
+ ##
112
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
113
+ ## +:multi+ => +true+), in which case the value of the argument will be
114
+ ## an array of arrays.
115
+ ##
116
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
117
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
118
+ ## is a multi-value argument as well as a multi-occurrence argument.
119
+ ## In thise case, Trollop assumes that it's not a multi-value argument.
120
+ ## If you want a multi-value, multi-occurrence argument with a default
121
+ ## value, you must specify +:type+ as well.
122
+
123
+ def opt(name, desc = "", opts = {}, &b)
124
+ opts[:callback] ||= b if block_given?
125
+ opts[:desc] ||= desc
126
+
127
+ o = Option.create(name, desc, opts)
128
+
129
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? o.name
130
+ raise ArgumentError, "long option name #{o.long.inspect} is already taken; please specify a (different) :long" if @long[o.long]
131
+ raise ArgumentError, "short option name #{o.short.inspect} is already taken; please specify a (different) :short" if @short[o.short]
132
+ @long[o.long] = o.name
133
+ @short[o.short] = o.name if o.short?
134
+ @specs[o.name] = o
135
+ @order << [:opt, o.name]
136
+ end
137
+
138
+ ## Sets the version string. If set, the user can request the version
139
+ ## on the commandline. Should probably be of the form "<program name>
140
+ ## <version number>".
141
+ def version(s = nil)
142
+ s ? @version = s : @version
143
+ end
144
+
145
+ ## Sets the usage string. If set the message will be printed as the
146
+ ## first line in the help (educate) output and ending in two new
147
+ ## lines.
148
+ def usage(s = nil)
149
+ s ? @usage = s : @usage
150
+ end
151
+
152
+ ## Adds a synopsis (command summary description) right below the
153
+ ## usage line, or as the first line if usage isn't specified.
154
+ def synopsis(s = nil)
155
+ s ? @synopsis = s : @synopsis
156
+ end
157
+
158
+ ## Adds text to the help display. Can be interspersed with calls to
159
+ ## #opt to build a multi-section help page.
160
+ def banner(s)
161
+ @order << [:text, s]
162
+ end
163
+ alias_method :text, :banner
164
+
165
+ ## Marks two (or more!) options as requiring each other. Only handles
166
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
167
+ ## better modeled with Trollop::die.
168
+ def depends(*syms)
169
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
170
+ @constraints << [:depends, syms]
171
+ end
172
+
173
+ ## Marks two (or more!) options as conflicting.
174
+ def conflicts(*syms)
175
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
176
+ @constraints << [:conflicts, syms]
177
+ end
178
+
179
+ ## Defines a set of words which cause parsing to terminate when
180
+ ## encountered, such that any options to the left of the word are
181
+ ## parsed as usual, and options to the right of the word are left
182
+ ## intact.
183
+ ##
184
+ ## A typical use case would be for subcommand support, where these
185
+ ## would be set to the list of subcommands. A subsequent Trollop
186
+ ## invocation would then be used to parse subcommand options, after
187
+ ## shifting the subcommand off of ARGV.
188
+ def stop_on(*words)
189
+ @stop_words = [*words].flatten
190
+ end
191
+
192
+ ## Similar to #stop_on, but stops on any unknown word when encountered
193
+ ## (unless it is a parameter for an argument). This is useful for
194
+ ## cases where you don't know the set of subcommands ahead of time,
195
+ ## i.e., without first parsing the global options.
196
+ def stop_on_unknown
197
+ @stop_on_unknown = true
198
+ end
199
+
200
+ ## Instead of displaying "Try --help for help." on an error
201
+ ## display the usage (via educate)
202
+ def educate_on_error
203
+ @educate_on_error = true
204
+ end
205
+
206
+ ## Parses the commandline. Typically called by Trollop::options,
207
+ ## but you can call it directly if you need more control.
208
+ ##
209
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
210
+ def parse(cmdline = ARGV)
211
+ vals = {}
212
+ required = {}
213
+
214
+ opt :version, "Print version and exit" if @version && ! (@specs[:version] || @long["version"])
215
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
216
+
217
+ @specs.each do |sym, opts|
218
+ required[sym] = true if opts.required?
219
+ vals[sym] = opts.default
220
+ vals[sym] = [] if opts.multi && !opts.default # multi arguments default to [], not nil
221
+ end
222
+
223
+ resolve_default_short_options!
224
+
225
+ ## resolve symbols
226
+ given_args = {}
227
+ @leftovers = each_arg cmdline do |arg, params|
228
+ ## handle --no- forms
229
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
230
+ ["--#{$1}", true]
231
+ else
232
+ [arg, false]
233
+ end
234
+
235
+ sym = case arg
236
+ when /^-([^-])$/ then @short[$1]
237
+ when /^--([^-]\S*)$/ then @long[$1] || @long["no-#{$1}"]
238
+ else raise CommandlineError, "invalid argument syntax: '#{arg}'"
239
+ end
240
+
241
+ sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
242
+
243
+ next 0 if ignore_invalid_options && !sym
244
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
245
+
246
+ if given_args.include?(sym) && !@specs[sym].multi?
247
+ raise CommandlineError, "option '#{arg}' specified multiple times"
248
+ end
249
+
250
+ given_args[sym] ||= {}
251
+ given_args[sym][:arg] = arg
252
+ given_args[sym][:negative_given] = negative_given
253
+ given_args[sym][:params] ||= []
254
+
255
+ # The block returns the number of parameters taken.
256
+ num_params_taken = 0
257
+
258
+ unless params.nil?
259
+ if @specs[sym].single_arg?
260
+ given_args[sym][:params] << params[0, 1] # take the first parameter
261
+ num_params_taken = 1
262
+ elsif @specs[sym].multi_arg?
263
+ given_args[sym][:params] << params # take all the parameters
264
+ num_params_taken = params.size
265
+ end
266
+ end
267
+
268
+ num_params_taken
269
+ end
270
+
271
+ ## check for version and help args
272
+ raise VersionNeeded if given_args.include? :version
273
+ raise HelpNeeded if given_args.include? :help
274
+
275
+ ## check constraint satisfaction
276
+ @constraints.each do |type, syms|
277
+ constraint_sym = syms.find { |sym| given_args[sym] }
278
+ next unless constraint_sym
279
+
280
+ case type
281
+ when :depends
282
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} requires --#{@specs[sym].long}" unless given_args.include? sym }
283
+ when :conflicts
284
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} conflicts with --#{@specs[sym].long}" if given_args.include?(sym) && (sym != constraint_sym) }
285
+ end
286
+ end
287
+
288
+ required.each do |sym, val|
289
+ raise CommandlineError, "option --#{@specs[sym].long} must be specified" unless given_args.include? sym
290
+ end
291
+
292
+ ## parse parameters
293
+ given_args.each do |sym, given_data|
294
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
295
+
296
+ opts = @specs[sym]
297
+ if params.empty? && !opts.flag?
298
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless opts.default
299
+ params << (opts.array_default? ? opts.default.clone : [opts.default])
300
+ end
301
+
302
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
303
+
304
+ case opts.type
305
+ when :flag
306
+ vals[sym] = (sym.to_s =~ /^no_/ ? negative_given : !negative_given)
307
+ when :int, :ints
308
+ vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
309
+ when :float, :floats
310
+ vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
311
+ when :string, :strings
312
+ vals[sym] = params.map { |pg| pg.map(&:to_s) }
313
+ when :io, :ios
314
+ vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
315
+ when :date, :dates
316
+ vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
317
+ end
318
+
319
+ if opts.single_arg?
320
+ if opts.multi? # multiple options, each with a single parameter
321
+ vals[sym] = vals[sym].map { |p| p[0] }
322
+ else # single parameter
323
+ vals[sym] = vals[sym][0][0]
324
+ end
325
+ elsif opts.multi_arg? && !opts.multi?
326
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
327
+ end
328
+ # else: multiple options, with multiple parameters
329
+
330
+ opts.callback.call(vals[sym]) if opts.callback
331
+ end
332
+
333
+ ## modify input in place with only those
334
+ ## arguments we didn't process
335
+ cmdline.clear
336
+ @leftovers.each { |l| cmdline << l }
337
+
338
+ ## allow openstruct-style accessors
339
+ class << vals
340
+ def method_missing(m, *_args)
341
+ self[m] || self[m.to_s]
342
+ end
343
+ end
344
+ vals
345
+ end
346
+
347
+ def parse_date_parameter(param, arg) #:nodoc:
348
+ begin
349
+ require 'chronic'
350
+ time = Chronic.parse(param)
351
+ rescue LoadError
352
+ # chronic is not available
353
+ end
354
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
355
+ rescue ArgumentError
356
+ raise CommandlineError, "option '#{arg}' needs a date"
357
+ end
358
+
359
+ ## Print the help message to +stream+.
360
+ def educate(stream = $stdout)
361
+ width # hack: calculate it now; otherwise we have to be careful not to
362
+ # call this unless the cursor's at the beginning of a line.
363
+ left = {}
364
+ @specs.each do |name, spec|
365
+ left[name] =
366
+ (spec.short? ? "-#{spec.short}, " : "") + "--#{spec.long}" +
367
+ case spec.type
368
+ when :flag then ""
369
+ when :int then "=<i>"
370
+ when :ints then "=<i+>"
371
+ when :string then "=<s>"
372
+ when :strings then "=<s+>"
373
+ when :float then "=<f>"
374
+ when :floats then "=<f+>"
375
+ when :io then "=<filename/uri>"
376
+ when :ios then "=<filename/uri+>"
377
+ when :date then "=<date>"
378
+ when :dates then "=<date+>"
379
+ end +
380
+ (spec.flag? && spec.default ? ", --no-#{spec.long}" : "")
381
+ end
382
+
383
+ leftcol_width = left.values.map(&:length).max || 0
384
+ rightcol_start = leftcol_width + 6 # spaces
385
+
386
+ unless @order.size > 0 && @order.first.first == :text
387
+ command_name = File.basename($0).gsub(/\.[^.]+$/, '')
388
+ stream.puts "Usage: #{command_name} #{@usage}\n" if @usage
389
+ stream.puts "#{@synopsis}\n" if @synopsis
390
+ stream.puts if @usage || @synopsis
391
+ stream.puts "#{@version}\n" if @version
392
+ stream.puts "Options:"
393
+ end
394
+
395
+ @order.each do |what, opt|
396
+ if what == :text
397
+ stream.puts wrap(opt)
398
+ next
399
+ end
400
+
401
+ spec = @specs[opt]
402
+ stream.printf " %-#{leftcol_width}s ", left[opt]
403
+ desc = spec.desc + begin
404
+ default_s = case spec.default
405
+ when $stdout then "<stdout>"
406
+ when $stdin then "<stdin>"
407
+ when $stderr then "<stderr>"
408
+ when Array
409
+ spec.default.join(", ")
410
+ else
411
+ spec.default.to_s
412
+ end
413
+
414
+ if spec.default
415
+ if spec.desc =~ /\.$/
416
+ " (Default: #{default_s})"
417
+ else
418
+ " (default: #{default_s})"
419
+ end
420
+ else
421
+ ""
422
+ end
423
+ end
424
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
425
+ end
426
+ end
427
+
428
+ def width #:nodoc:
429
+ @width ||= if $stdout.tty?
430
+ begin
431
+ require 'io/console'
432
+ w = IO.console.winsize.last
433
+ w.to_i > 0 ? w : 80
434
+ rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
435
+ legacy_width
436
+ end
437
+ else
438
+ 80
439
+ end
440
+ end
441
+
442
+ def legacy_width
443
+ # Support for older Rubies where io/console is not available
444
+ `tput cols`.to_i
445
+ rescue Errno::ENOENT
446
+ 80
447
+ end
448
+ private :legacy_width
449
+
450
+ def wrap(str, opts = {}) # :nodoc:
451
+ if str == ""
452
+ [""]
453
+ else
454
+ inner = false
455
+ str.split("\n").map do |s|
456
+ line = wrap_line s, opts.merge(:inner => inner)
457
+ inner = true
458
+ line
459
+ end.flatten
460
+ end
461
+ end
462
+
463
+ ## The per-parser version of Trollop::die (see that for documentation).
464
+ def die(arg, msg = nil, error_code = nil)
465
+ if msg
466
+ $stderr.puts "Error: argument --#{@specs[arg].long} #{msg}."
467
+ else
468
+ $stderr.puts "Error: #{arg}."
469
+ end
470
+ if @educate_on_error
471
+ $stderr.puts
472
+ educate $stderr
473
+ else
474
+ $stderr.puts "Try --help for help."
475
+ end
476
+ exit(error_code || -1)
477
+ end
478
+
479
+ private
480
+
481
+ ## yield successive arg, parameter pairs
482
+ def each_arg(args)
483
+ remains = []
484
+ i = 0
485
+
486
+ until i >= args.length
487
+ return remains += args[i..-1] if @stop_words.member? args[i]
488
+ case args[i]
489
+ when /^--$/ # arg terminator
490
+ return remains += args[(i + 1)..-1]
491
+ when /^--(\S+?)=(.*)$/ # long argument with equals
492
+ yield "--#{$1}", [$2]
493
+ i += 1
494
+ when /^--(\S+)$/ # long argument
495
+ params = collect_argument_parameters(args, i + 1)
496
+ if params.empty?
497
+ yield args[i], nil
498
+ i += 1
499
+ else
500
+ num_params_taken = yield args[i], params
501
+ unless num_params_taken
502
+ if @stop_on_unknown
503
+ return remains += args[i + 1..-1]
504
+ else
505
+ remains += params
506
+ end
507
+ end
508
+ i += 1 + num_params_taken
509
+ end
510
+ when /^-(\S+)$/ # one or more short arguments
511
+ shortargs = $1.split(//)
512
+ shortargs.each_with_index do |a, j|
513
+ if j == (shortargs.length - 1)
514
+ params = collect_argument_parameters(args, i + 1)
515
+ if params.empty?
516
+ yield "-#{a}", nil
517
+ i += 1
518
+ else
519
+ num_params_taken = yield "-#{a}", params
520
+ unless num_params_taken
521
+ if @stop_on_unknown
522
+ return remains += args[i + 1..-1]
523
+ else
524
+ remains += params
525
+ end
526
+ end
527
+ i += 1 + num_params_taken
528
+ end
529
+ else
530
+ yield "-#{a}", nil
531
+ end
532
+ end
533
+ else
534
+ if @stop_on_unknown
535
+ return remains += args[i..-1]
536
+ else
537
+ remains << args[i]
538
+ i += 1
539
+ end
540
+ end
541
+ end
542
+
543
+ remains
544
+ end
545
+
546
+ def parse_integer_parameter(param, arg)
547
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^-?[\d_]+$/
548
+ param.to_i
549
+ end
550
+
551
+ def parse_float_parameter(param, arg)
552
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
553
+ param.to_f
554
+ end
555
+
556
+ def parse_io_parameter(param, arg)
557
+ if param =~ /^(stdin|-)$/i
558
+ $stdin
559
+ else
560
+ require 'open-uri'
561
+ begin
562
+ open param
563
+ rescue SystemCallError => e
564
+ raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
565
+ end
566
+ end
567
+ end
568
+
569
+ def collect_argument_parameters(args, start_at)
570
+ params = []
571
+ pos = start_at
572
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
573
+ params << args[pos]
574
+ pos += 1
575
+ end
576
+ params
577
+ end
578
+
579
+ def resolve_default_short_options!
580
+ @order.each do |type, name|
581
+ opts = @specs[name]
582
+ next if type != :opt || opts.short
583
+
584
+ c = opts.long.split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
585
+ if c # found a character to use
586
+ opts.short = c
587
+ @short[c] = name
588
+ end
589
+ end
590
+ end
591
+
592
+ def wrap_line(str, opts = {})
593
+ prefix = opts[:prefix] || 0
594
+ width = opts[:width] || (self.width - 1)
595
+ start = 0
596
+ ret = []
597
+ until start > str.length
598
+ nextt =
599
+ if start + width >= str.length
600
+ str.length
601
+ else
602
+ x = str.rindex(/\s/, start + width)
603
+ x = str.index(/\s/, start) if x && x < start
604
+ x || str.length
605
+ end
606
+ ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start...nextt]
607
+ start = nextt + 1
608
+ end
609
+ ret
610
+ end
611
+
612
+ ## instance_eval but with ability to handle block arguments
613
+ ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
614
+ def cloaker(&b)
615
+ (class << self; self; end).class_eval do
616
+ define_method :cloaker_, &b
617
+ meth = instance_method :cloaker_
618
+ remove_method :cloaker_
619
+ meth
620
+ end
621
+ end
622
+ end
623
+
624
+ ## The option for each flag
625
+ class Option
626
+ ## The set of values that indicate a flag option when passed as the
627
+ ## +:type+ parameter of #opt.
628
+ FLAG_TYPES = [:flag, :bool, :boolean]
629
+
630
+ ## The set of values that indicate a single-parameter (normal) option when
631
+ ## passed as the +:type+ parameter of #opt.
632
+ ##
633
+ ## A value of +io+ corresponds to a readable IO resource, including
634
+ ## a filename, URI, or the strings 'stdin' or '-'.
635
+ SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date]
636
+
637
+ ## The set of values that indicate a multiple-parameter option (i.e., that
638
+ ## takes multiple space-separated values on the commandline) when passed as
639
+ ## the +:type+ parameter of #opt.
640
+ MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates]
641
+
642
+ ## The complete set of legal values for the +:type+ parameter of #opt.
643
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
644
+
645
+ attr_accessor :name, :opts
646
+
647
+ def initialize(name, desc="", opts={}, &b)
648
+ ## fill in :type
649
+ opts[:type] = # normalize
650
+ case opts[:type]
651
+ when :boolean, :bool then :flag
652
+ when :integer then :int
653
+ when :integers then :ints
654
+ when :double then :float
655
+ when :doubles then :floats
656
+ when Class
657
+ case opts[:type].name
658
+ when 'TrueClass',
659
+ 'FalseClass' then :flag
660
+ when 'String' then :string
661
+ when 'Integer' then :int
662
+ when 'Float' then :float
663
+ when 'IO' then :io
664
+ when 'Date' then :date
665
+ else
666
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
667
+ end
668
+ when nil then nil
669
+ else
670
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
671
+ opts[:type]
672
+ end
673
+
674
+ ## for options with :multi => true, an array default doesn't imply
675
+ ## a multi-valued argument. for that you have to specify a :type
676
+ ## as well. (this is how we disambiguate an ambiguous situation;
677
+ ## see the docs for Parser#opt for details.)
678
+ disambiguated_default = if opts[:multi] && opts[:default].kind_of?(Array) && !opts[:type]
679
+ opts[:default].first
680
+ else
681
+ opts[:default]
682
+ end
683
+
684
+ type_from_default =
685
+ case disambiguated_default
686
+ when Integer then :int
687
+ when Numeric then :float
688
+ when TrueClass,
689
+ FalseClass then :flag
690
+ when String then :string
691
+ when IO then :io
692
+ when Date then :date
693
+ when Array
694
+ if opts[:default].empty?
695
+ if opts[:type]
696
+ raise ArgumentError, "multiple argument type must be plural" unless MULTI_ARG_TYPES.include?(opts[:type])
697
+ nil
698
+ else
699
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
700
+ end
701
+ else
702
+ case opts[:default][0] # the first element determines the types
703
+ when Integer then :ints
704
+ when Numeric then :floats
705
+ when String then :strings
706
+ when IO then :ios
707
+ when Date then :dates
708
+ else
709
+ raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
710
+ end
711
+ end
712
+ when nil then nil
713
+ else
714
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
715
+ end
716
+
717
+ 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
718
+
719
+ opts[:type] = opts[:type] || type_from_default || :flag
720
+
721
+ ## fill in :long
722
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
723
+ opts[:long] = case opts[:long]
724
+ when /^--([^-].*)$/ then $1
725
+ when /^[^-]/ then opts[:long]
726
+ else raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
727
+ end
728
+
729
+ ## fill in :short
730
+ opts[:short] = opts[:short].to_s if opts[:short] && opts[:short] != :none
731
+ opts[:short] = case opts[:short]
732
+ when /^-(.)$/ then $1
733
+ when nil, :none, /^.$/ then opts[:short]
734
+ else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
735
+ end
736
+
737
+ if opts[:short]
738
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ ::Trollop::Parser::INVALID_SHORT_ARG_REGEX
739
+ end
740
+
741
+ ## fill in :default for flags
742
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
743
+
744
+ ## autobox :default for :multi (multi-occurrence) arguments
745
+ opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].kind_of?(Array)
746
+
747
+ ## fill in :multi
748
+ opts[:multi] ||= false
749
+
750
+ self.name = name
751
+ self.opts = opts
752
+ end
753
+
754
+ def key?(name)
755
+ opts.key?(name)
756
+ end
757
+
758
+ def type ; opts[:type] ; end
759
+ def flag? ; type == :flag ; end
760
+ def single_arg?
761
+ SINGLE_ARG_TYPES.include?(type)
762
+ end
763
+
764
+ def multi ; opts[:multi] ; end
765
+ alias multi? multi
766
+
767
+ def multi_arg?
768
+ MULTI_ARG_TYPES.include?(type)
769
+ end
770
+
771
+ def default ; opts[:default] ; end
772
+ #? def multi_default ; opts.default || opts.multi && [] ; end
773
+ def array_default? ; opts[:default].kind_of?(Array) ; end
774
+
775
+ def short ; opts[:short] ; end
776
+ def short? ; short && short != :none ; end
777
+ # not thrilled about this
778
+ def short=(val) ; opts[:short] = val ; end
779
+ def long ; opts[:long] ; end
780
+ def callback ; opts[:callback] ; end
781
+ def desc ; opts[:desc] ; end
782
+
783
+ def required? ; opts[:required] ; end
784
+
785
+ def self.create(name, desc="", opts={})
786
+ new(name, desc, opts)
787
+ end
788
+ end
789
+
790
+ ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser,
791
+ ## passes the block to it, then parses +args+ with it, handling any errors or
792
+ ## requests for help or version information appropriately (and then exiting).
793
+ ## Modifies +args+ in place. Returns a hash of option values.
794
+ ##
795
+ ## The block passed in should contain zero or more calls to +opt+
796
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
797
+ ## probably a call to +version+ (Parser#version).
798
+ ##
799
+ ## The returned block contains a value for every option specified with
800
+ ## +opt+. The value will be the value given on the commandline, or the
801
+ ## default value if the option was not specified on the commandline. For
802
+ ## every option specified on the commandline, a key "<option
803
+ ## name>_given" will also be set in the hash.
804
+ ##
805
+ ## Example:
806
+ ##
807
+ ## require 'trollop'
808
+ ## opts = Trollop::options do
809
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
810
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
811
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
812
+ ## end
813
+ ##
814
+ ## ## if called with no arguments
815
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
816
+ ##
817
+ ## ## if called with --monkey
818
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
819
+ ##
820
+ ## See more examples at http://trollop.rubyforge.org.
821
+ def options(args = ARGV, *a, &b)
822
+ @last_parser = Parser.new(*a, &b)
823
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
824
+ end
825
+
826
+ ## If Trollop::options doesn't do quite what you want, you can create a Parser
827
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
828
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
829
+ ## have these handled for you in the standard manner (e.g. show the help
830
+ ## and then exit upon an HelpNeeded exception), call your code from within
831
+ ## a block passed to this method.
832
+ ##
833
+ ## Note that this method will call System#exit after handling an exception!
834
+ ##
835
+ ## Usage example:
836
+ ##
837
+ ## require 'trollop'
838
+ ## p = Trollop::Parser.new do
839
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
840
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
841
+ ## end
842
+ ##
843
+ ## opts = Trollop::with_standard_exception_handling p do
844
+ ## o = p.parse ARGV
845
+ ## raise Trollop::HelpNeeded if ARGV.empty? # show help screen
846
+ ## o
847
+ ## end
848
+ ##
849
+ ## Requires passing in the parser object.
850
+
851
+ def with_standard_exception_handling(parser)
852
+ yield
853
+ rescue CommandlineError => e
854
+ parser.die(e.message, nil, e.error_code)
855
+ rescue HelpNeeded
856
+ parser.educate
857
+ exit
858
+ rescue VersionNeeded
859
+ puts parser.version
860
+ exit
861
+ end
862
+
863
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
864
+ ## 'msg', and dies. Example:
865
+ ##
866
+ ## options do
867
+ ## opt :volume, :default => 0.0
868
+ ## end
869
+ ##
870
+ ## die :volume, "too loud" if opts[:volume] > 10.0
871
+ ## die :volume, "too soft" if opts[:volume] < 0.1
872
+ ##
873
+ ## In the one-argument case, simply print that message, a notice
874
+ ## about -h, and die. Example:
875
+ ##
876
+ ## options do
877
+ ## opt :whatever # ...
878
+ ## end
879
+ ##
880
+ ## Trollop::die "need at least one filename" if ARGV.empty?
881
+ def die(arg, msg = nil, error_code = nil)
882
+ if @last_parser
883
+ @last_parser.die arg, msg, error_code
884
+ else
885
+ raise ArgumentError, "Trollop::die can only be called after Trollop::options"
886
+ end
887
+ end
888
+
889
+ ## Displays the help message and dies. Example:
890
+ ##
891
+ ## options do
892
+ ## opt :volume, :default => 0.0
893
+ ## banner <<-EOS
894
+ ## Usage:
895
+ ## #$0 [options] <name>
896
+ ## where [options] are:
897
+ ## EOS
898
+ ## end
899
+ ##
900
+ ## Trollop::educate if ARGV.empty?
901
+ def educate
902
+ if @last_parser
903
+ @last_parser.educate
904
+ exit
905
+ else
906
+ raise ArgumentError, "Trollop::educate can only be called after Trollop::options"
907
+ end
908
+ end
909
+
910
+ module_function :options, :die, :educate, :with_standard_exception_handling
911
+ end # module
@@ -0,0 +1,5 @@
1
+ module Darksky
2
+
3
+ Version = '0.0.1'
4
+
5
+ end
@@ -0,0 +1,3 @@
1
+ require 'darksky-ruby/logger'
2
+ require 'darksky-ruby/api'
3
+ require 'darksky-ruby/http'
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: darksky-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ken J.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-07-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Darksky library written in pure Ruby without external dependancy.
14
+ email:
15
+ - kenjij@gmail.com
16
+ executables:
17
+ - darksky
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - bin/darksky
22
+ - darksky-ruby.gemspec
23
+ - lib/darksky-ruby.rb
24
+ - lib/darksky-ruby/api.rb
25
+ - lib/darksky-ruby/http.rb
26
+ - lib/darksky-ruby/logger.rb
27
+ - lib/darksky-ruby/trollop.rb
28
+ - lib/darksky-ruby/version.rb
29
+ homepage: https://github.com/kenjij/darksky-ruby
30
+ licenses:
31
+ - MIT
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '2.0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 2.6.12
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Pure simple Ruby based Darksky REST library
53
+ test_files: []