flon 0.1.0

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
+ SHA256:
3
+ metadata.gz: dcd15fb29f7a61dc56644751c6b3f3f6a6181e148a386e71b2b9d5197048f3dd
4
+ data.tar.gz: f12632af7ac2b14a6ba79a77fcb8cdc0d6905de5ad47bb59bc73a5d5b39d3089
5
+ SHA512:
6
+ metadata.gz: c552c94ec88849cdfb0d6e68b584609154f7ff73f9d486ee95860bca47416f2bb15327a6f97890810c5c8de2cb9a0e75c8503985d000448115e9d2e518e66bd7
7
+ data.tar.gz: 9051e7d9f471a94b78a3731e2996eee2e61c9d7f10babc144873314a1d4cbc8cd6b2b1d1036ae99da34fd412b56411e0681d2872e91b731b3996fe914628723f
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+
4
+ Layout/EndOfLine:
5
+ EnforcedStyle: lf
6
+
7
+ Metrics/LineLength:
8
+ Max: 120
9
+
10
+ Metrics/BlockLength:
11
+ Exclude:
12
+ - 'spec/**/*.rb'
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.3
7
+ before_install: gem install bundler -v 2.0.2
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ - README.md LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in flon.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 unleashy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # Flon
2
+
3
+ Flon, the API maker that uses JSON. It focuses on not doing any magic: it’s
4
+ just a thin and convenient wrapper over Rack, with a hopefully obvious DSL.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'flon'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install flon
21
+
22
+ ## Usage
23
+
24
+ ### Basic usage
25
+
26
+ To use Flon, simply make a plain class extending `Flon::DSL`. This will put the
27
+ router DSL in your class scope.
28
+
29
+ ```ruby
30
+ require 'flon'
31
+
32
+ class MyApi
33
+ extend Flon::DSL
34
+
35
+ def initialize
36
+ @names = []
37
+ end
38
+
39
+ namespace '/names' do
40
+ get '/'
41
+ attr_reader :names
42
+
43
+ post '/new'
44
+ def add_name(_params, body)
45
+ @names << body
46
+ end
47
+
48
+ namespace '/:index' do
49
+ get '/'
50
+ def name_by_index(params)
51
+ @names[params[:index]]
52
+ end
53
+
54
+ put '/edit'
55
+ def change_name(params, body)
56
+ @names[params[:index]] = body
57
+ end
58
+ end
59
+ end
60
+ end
61
+ ```
62
+
63
+ You can then create a `Flon::Api` using this:
64
+
65
+ ```ruby
66
+ api = Flon::Api.new(MyApi.new)
67
+ ```
68
+
69
+ Note how you initialise the `MyApi` normally—you can use this for dependency
70
+ injection without a headache.
71
+
72
+ `Flon::Api` is a bog-standard Rack app, so you can just use it in `config.ru`,
73
+ or whatever method you use.
74
+
75
+ ### Routing
76
+
77
+ The DSL defines methods for routing based on every HTTP request method, except
78
+ HEAD, TRACE, OPTIONS, and CONNECT. HEAD is handled by GET routes but the body
79
+ is discarded.
80
+
81
+ When you call a routing method, the next method you define will be bound to
82
+ that route.
83
+
84
+ ```ruby
85
+ get '/route'
86
+ def get_route
87
+ # matches GET /route and HEAD /route
88
+ end
89
+
90
+ post '/route'
91
+ def post_route
92
+ # matches POST /route
93
+ end
94
+
95
+ put '/route'
96
+ def put_route
97
+ # matches PUT /route
98
+ end
99
+
100
+ delete '/route'
101
+ def delete_route
102
+ # matches DELETE /route
103
+ end
104
+
105
+ patch '/route'
106
+ def patch_route
107
+ # matches PATCH /route
108
+ end
109
+ ```
110
+
111
+ Each routing DSL method takes a single route pattern as a `String`. The route
112
+ pattern is the exact same as [Sinatra’s](https://github.com/sinatra/mustermann/blob/master/mustermann/README.md#-sinatra-pattern).
113
+
114
+ The bound method is called when an HTTP request matches that route with that
115
+ HTTP method.
116
+
117
+ The method will receive two arguments, in that order:
118
+
119
+ 1. A parameters hash, containing the query string parameters and the route
120
+ parameters;
121
+ 2. A body object, containing the HTTP request’s body parsed with `JSON.parse`.
122
+ For GETs, HEADs, and DELETEs, this will be `nil`.
123
+
124
+ #### Namespaces
125
+
126
+ You can also **namespace** routes:
127
+
128
+ ```ruby
129
+ namespace '/users' do
130
+ get '/'
131
+ attr_reader :users # GET /users
132
+
133
+ post '/new'
134
+ def add_user(_params, user) # /users/new
135
+ @users << user
136
+ end
137
+
138
+ namespace '/:id' do
139
+ get
140
+ def user_by_id(params) # GET /users/:id
141
+ @users[params[:id]]
142
+ end
143
+
144
+ put '/edit'
145
+ def edit_user(params, body) # PUT /users/:id/edit
146
+ @users[params[:id]] = body
147
+ end
148
+ end
149
+ end
150
+ ```
151
+
152
+ Namespaces are entirely “virtual”, that is, they just concatenate their route
153
+ patterns with their children, so they’re just there for DRYness.
154
+
155
+ ### Versioning
156
+
157
+ The `version` method applies a namespace throughout every route:
158
+
159
+ ```ruby
160
+ version '/v1'
161
+
162
+ get '/yes'
163
+ def yes
164
+ 'yes'
165
+ end
166
+
167
+ get '/no'
168
+ def no
169
+ 'no'
170
+ end
171
+ ```
172
+
173
+ Going to `/yes` will give you a 404, but going to `/v1/yes` will give you
174
+ `"yes"`. The same thing happens with the `/no` route.
175
+
176
+ In reality, `version` is just an alias for `namespace`.
177
+
178
+ ### Responding
179
+
180
+ Whatever is returned by the route’s method is `#to_json`’d and sent back. If
181
+ you want to return a custom status code, return a `Flon::Response` object,
182
+ which takes the status code as the first argument and the body as the second
183
+ argument of its constructor:
184
+
185
+ ```ruby
186
+ get '/admin'
187
+ def admin
188
+ Flon::Response.new(403, 'No. Go away.')
189
+ end
190
+ ```
191
+
192
+ ## Development
193
+
194
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
195
+ `rake` to run rubocop and the tests. You can also run `bin/console` for an
196
+ interactive prompt that will allow you to experiment.
197
+
198
+ To install this gem onto your local machine, run `bundle exec rake install`. To
199
+ release a new version, update the version number in `version.rb`, and then run
200
+ `bundle exec rake release`, which will create a git tag for the version, push
201
+ git commits and tags, and push the `.gem` file to
202
+ [rubygems.org](https://rubygems.org).
203
+
204
+ ## Contributing
205
+
206
+ Bug reports and pull requests are welcome on GitHub at
207
+ [https://github.com/unleashy/flon](https://github.com/unleashy/flon).
208
+
209
+ ## License
210
+
211
+ The gem is available as open source under the terms of the
212
+ [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+ RuboCop::RakeTask.new(:rubocop)
10
+
11
+ task default: %i[rubocop spec]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'flon'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/flon.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'flon/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'flon'
9
+ spec.version = Flon::VERSION
10
+ spec.authors = ['unleashy']
11
+ spec.email = ['unleashy@users.noreply.github.com']
12
+
13
+ spec.summary = 'Flon, the API maker that uses JSON'
14
+ spec.homepage = 'https://github.com/unleashy/flon'
15
+ spec.license = 'MIT'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = spec.homepage
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'mustermann', '~> 1.0'
30
+ spec.add_dependency 'rack', '~> 2.0'
31
+
32
+ spec.add_development_dependency 'bundler', '~> 2.0'
33
+ spec.add_development_dependency 'rake', '~> 10.0'
34
+ spec.add_development_dependency 'rspec', '~> 3.0'
35
+ spec.add_development_dependency 'rubocop', '>= 0.74.0'
36
+ spec.add_development_dependency 'simplecov', '>= 0.17.0'
37
+ end
data/lib/flon.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'flon/version'
4
+ require 'flon/router'
5
+ require 'flon/dsl'
6
+ require 'flon/api'
7
+
8
+ # The main module.
9
+ module Flon
10
+ # Flon's main error class.
11
+ class Error < StandardError; end
12
+ end
data/lib/flon/api.rb ADDED
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+ require 'json'
5
+
6
+ module Flon
7
+ # Represents a response, with an integer status and a body that will be
8
+ # #to_json'd.
9
+ #
10
+ # @!attribute [r] status
11
+ # @return [Integer] the status to use when responding
12
+ #
13
+ # @!attribute [r] body
14
+ # @return [#to_json] the object to respond with that shall be #to_json'd
15
+ Response = Struct.new(:status, :body)
16
+
17
+ # This class is Flon's Rack application.
18
+ class Api
19
+ # Creates a new {Api} object with the given API.
20
+ #
21
+ # @param [Object] api the API to use; its class must respond to #router and
22
+ # the result must be a {Router}.
23
+ # @return [Api] the newly constructed object
24
+ def initialize(api)
25
+ raise ArgumentError, "given API's class does not respond to #routes" unless valid_api?(api)
26
+
27
+ @api = api
28
+ @router = api.class.router
29
+ end
30
+
31
+ # Implements the Rack interface.
32
+ #
33
+ # @param [Hash] env a rack environment
34
+ # @return [Array] a rack-suitable response
35
+ def call(env)
36
+ request = Rack::Request.new(env)
37
+ response = dispatch(request)
38
+ [response.status, { 'Content-Type' => 'application/json' }, [response.body]]
39
+ end
40
+
41
+ private
42
+
43
+ def dispatch(request)
44
+ method = http_method_to_symbol(request.request_method)
45
+ path = request.path_info
46
+
47
+ match = @router.match(method, path)
48
+ case match
49
+ when :bad_path then Response.new(404, '"404 Not Found"')
50
+ when :bad_method then Response.new(405, '"405 Method Not Allowed"')
51
+ else call_action(request.params, match, receives_body?(method) ? request.body.read : nil)
52
+ end
53
+ end
54
+
55
+ def call_action(params, match, body)
56
+ params = params.transform_keys(&:to_sym).merge(match.params)
57
+ responsify(call_respect_arity(match.action.bind(@api), params, body))
58
+ end
59
+
60
+ def responsify(result)
61
+ if result.is_a?(Response)
62
+ result.tap { |it| it.body = it.body.to_json }
63
+ else
64
+ Response.new(200, result.to_json)
65
+ end
66
+ end
67
+
68
+ def call_respect_arity(method, *args)
69
+ arity = method.arity
70
+ arity.negative? ? method.call(*args) : method.call(*args[0...arity])
71
+ end
72
+
73
+ def http_method_to_symbol(http_method)
74
+ http_method.downcase.to_sym
75
+ end
76
+
77
+ def receives_body?(http_method)
78
+ http_method != :get && http_method != :delete
79
+ end
80
+
81
+ def valid_api?(api)
82
+ api.class.respond_to?(:router) && api.class.router.is_a?(Flon::Router)
83
+ end
84
+ end
85
+ end
data/lib/flon/dsl.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'flon/router'
4
+
5
+ module Flon
6
+ # This module holds Flon's DSL. You should extend it in your class.
7
+ #
8
+ # @example
9
+ # class BreadApi
10
+ # extend Flon::DSL
11
+ #
12
+ # get '/bread'
13
+ # def bread
14
+ # 'bread.'
15
+ # end
16
+ # end
17
+ module DSL
18
+ # @!group Routing
19
+
20
+ # @!method get(path)
21
+ # Creates a GET route from path, bound to the next defined method.
22
+ # @param [String] path the path to bind to
23
+ # @return [void]
24
+ # @!method post(path)
25
+ # Creates a POST route from path, bound to the next defined method.
26
+ # @param [String] path the path to bind to
27
+ # @return [void]
28
+ # @!method put(path)
29
+ # Creates a PUT route from path, bound to the next defined method.
30
+ # @param [String] path the path to bind to
31
+ # @return [void]
32
+ # @!method delete(path)
33
+ # Creates a DELETE route from path, bound to the next defined method.
34
+ # @param [String] path the path to bind to
35
+ # @return [void]
36
+ # @!method patch(path)
37
+ # Creates a PATCH route from path, bound to the next defined method.
38
+ # @param [String] path the path to bind to
39
+ # @return [void]
40
+ %i[get post put delete patch].each do |http_method|
41
+ define_method(http_method) do |path|
42
+ @current_route = [http_method, namespaces.join + path]
43
+ end
44
+ end
45
+
46
+ # Creates a namespace of the given path. If a block is given, the namespace
47
+ # is local to the block. If not, it is applied to the rest of the API. You
48
+ # may not call this method without a block twice.
49
+ #
50
+ # @param [String] path the path to use
51
+ # @return [void]
52
+ def namespace(path)
53
+ if block_given?
54
+ namespaces.push(path)
55
+ yield
56
+ namespaces.pop
57
+ else
58
+ unless namespaces.empty?
59
+ raise Flon::Error, 'cannot declare a nested global namespace or declare a global namespace twice'
60
+ end
61
+
62
+ namespaces.push(path)
63
+ end
64
+ end
65
+
66
+ alias version namespace
67
+
68
+ # @!endgroup
69
+
70
+ # @api private
71
+ def router
72
+ @router ||= Flon::Router.new
73
+ end
74
+
75
+ # @api private
76
+ def namespaces
77
+ @namespaces ||= []
78
+ end
79
+
80
+ # @api private
81
+ def method_added(name)
82
+ return unless @current_route
83
+
84
+ http_method, path = @current_route
85
+ router.add_route(http_method, path, instance_method(name))
86
+
87
+ @current_route = nil
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mustermann/sinatra'
4
+
5
+ module Flon
6
+ # Handles a set of routes mapped to actions. Note that the Router API is
7
+ # mainly for private Flon use.
8
+ class Router
9
+ # A route's match is encapsulated in this struct. {#action} has the action
10
+ # associated with the route matched against and {#params} has the parameters
11
+ # hash.
12
+ #
13
+ # @!attribute [r] action
14
+ # @return the route's action
15
+ #
16
+ # @!attribute [r] params
17
+ # @return [Hash{Symbol => Object}] the route's bound parameters
18
+ Match = Struct.new(:action, :params)
19
+
20
+ # The HTTP methods as symbols that this router will handle.
21
+ VALID_HTTP_METHODS = %i[get post put delete patch].freeze
22
+
23
+ # @return [Array<Mustermann::Sinatra, Hash{Symbol => Object}>] the current route set
24
+ attr_reader :routes
25
+
26
+ # Creates a new Router with an empty route set.
27
+ # @return [Router] the newly constructed object
28
+ def initialize
29
+ @routes = []
30
+ end
31
+
32
+ # Adds a route to this router's route set.
33
+ #
34
+ # @param [Symbol] method the method to use
35
+ # @param [String] path the path to use
36
+ # @param [Object] action the action to use
37
+ # @return [self] this {Router}
38
+ # @raise [ArgumentError] if method is not a valid HTTP method or the given
39
+ # route is already registered
40
+ def add_route(method, path, action) # rubocop:disable Metrics/MethodLength
41
+ raise ArgumentError, "#{method} is not a valid HTTP method" unless valid_method?(method)
42
+ raise ArgumentError, "route #{method.upcase} #{path} is already registered" if routing_to?(method, path)
43
+
44
+ route = @routes.find { |it| it[0].to_s == path }
45
+ if route
46
+ route[1][method] = action
47
+ else
48
+ route = [Mustermann::Sinatra.new(path), { method => action }]
49
+ @routes << route
50
+ end
51
+
52
+ route[1][:head] = action if method == :get
53
+
54
+ self
55
+ end
56
+
57
+ # Matches a method and path against this router's route set.
58
+ #
59
+ # @param [Symbol] method the HTTP method to match against
60
+ # @param [String] path the path to match against
61
+ # @return [RouteMatch, :bad_path, :bad_method] if successful, a RouteMatch;
62
+ # if not, :bad_method if the path exists but not bound to that HTTP
63
+ # method, :bad_path otherwise
64
+ def match(method, path)
65
+ @routes.each do |route|
66
+ params = route[0].params(path)
67
+ next unless params
68
+
69
+ action = route[1][method]
70
+ return :bad_method unless action
71
+
72
+ return Match.new(action, params.transform_keys(&:to_sym))
73
+ end
74
+
75
+ :bad_path
76
+ end
77
+
78
+ # @return [Boolean] true if this router has a route with that method and
79
+ # path in its route set, false otherwise
80
+ def routing_to?(method, path)
81
+ @routes.any? { |it| it[0].to_s == path && it[1].include?(method) }
82
+ end
83
+
84
+ private
85
+
86
+ def valid_method?(method)
87
+ VALID_HTTP_METHODS.include?(method)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flon
4
+ # Flon's version.
5
+ VERSION = '0.1.0'
6
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - unleashy
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-09-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mustermann
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.74.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.74.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 0.17.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 0.17.0
111
+ description:
112
+ email:
113
+ - unleashy@users.noreply.github.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".rubocop.yml"
121
+ - ".travis.yml"
122
+ - ".yardopts"
123
+ - Gemfile
124
+ - LICENSE.txt
125
+ - README.md
126
+ - Rakefile
127
+ - bin/console
128
+ - bin/setup
129
+ - flon.gemspec
130
+ - lib/flon.rb
131
+ - lib/flon/api.rb
132
+ - lib/flon/dsl.rb
133
+ - lib/flon/router.rb
134
+ - lib/flon/version.rb
135
+ homepage: https://github.com/unleashy/flon
136
+ licenses:
137
+ - MIT
138
+ metadata:
139
+ homepage_uri: https://github.com/unleashy/flon
140
+ source_code_uri: https://github.com/unleashy/flon
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.7.6
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Flon, the API maker that uses JSON
161
+ test_files: []