flame 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c69eaa9cc9a97a1ad4962f3e54011450dc1e1f11
4
+ data.tar.gz: 91d5016a225a54dbd43b36b8ef12b07f9b174051
5
+ SHA512:
6
+ metadata.gz: 669fed7b35fc590a1c895292f704fe2d20a112189c34e14ee87fbf95ae70441f425a45648d070d1cf20809077234cbfc716809394290141d74f062dc978772b5
7
+ data.tar.gz: 7ec7a41b6c8ba4b58c83ad613df1f54280504d3cbbb0f22bc27cb7f4827f16e1fb8bdb269b821fe75abb6be69c9c1e0998bd5323bae54d74b1fd8be62e0bb920
data/lib/flame.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'gorilla-patch/string'
2
+
3
+ require_relative './flame/application'
4
+ require_relative './flame/controller'
@@ -0,0 +1,43 @@
1
+ require_relative './router'
2
+ require_relative './dispatcher'
3
+
4
+ module Flame
5
+ ## Core class, like Framework::Application
6
+ class Application
7
+ ## Framework configuration
8
+ def self.config
9
+ @config ||= {}
10
+ end
11
+
12
+ def config
13
+ self.class.config
14
+ end
15
+
16
+ def self.inherited(app)
17
+ root_dir = File.dirname(caller[0].split(':')[0])
18
+ app.config.merge!(
19
+ root_dir: root_dir,
20
+ public_dir: File.join(root_dir, 'public'),
21
+ views_dir: File.join(root_dir, 'views')
22
+ )
23
+ end
24
+
25
+ ## Init function
26
+ def call(env)
27
+ Dispatcher.new(self, env).run!
28
+ end
29
+
30
+ def self.mount(ctrl, path = nil, &block)
31
+ router.add_controller(ctrl, path, block)
32
+ end
33
+
34
+ ## Router for routing
35
+ def self.router
36
+ @router ||= Flame::Router.new
37
+ end
38
+
39
+ def router
40
+ self.class.router
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ require_relative './render'
2
+
3
+ module Flame
4
+ ## Class for controllers helpers, like Framework::Controller
5
+ class Controller
6
+ include Flame::Render
7
+
8
+ def initialize(dispatcher)
9
+ @dispatcher = dispatcher
10
+ end
11
+
12
+ def config
13
+ @dispatcher.config
14
+ end
15
+
16
+ def params
17
+ @dispatcher.params
18
+ end
19
+
20
+ def path_to(*params)
21
+ @dispatcher.path_to(*params)
22
+ end
23
+
24
+ ## TODO: Add more helpers
25
+ end
26
+ end
@@ -0,0 +1,102 @@
1
+ require 'rack'
2
+
3
+ module Flame
4
+ ## Class initialize when Application.call(env) invoked
5
+ ## For new request and response
6
+ class Dispatcher
7
+ def initialize(app, env)
8
+ @app = app
9
+ @env = env
10
+ end
11
+
12
+ def run!
13
+ body = catch :halt do
14
+ try_route ||
15
+ try_static ||
16
+ try_static(File.join(__dir__, '..', '..', 'public')) ||
17
+ halt(404)
18
+ end
19
+ response.write body
20
+ response.finish
21
+ end
22
+
23
+ ## Helpers
24
+ def config
25
+ @app.config
26
+ end
27
+
28
+ def request
29
+ @request ||= Rack::Request.new(@env)
30
+ end
31
+
32
+ def params
33
+ request.params
34
+ end
35
+
36
+ def response
37
+ @response ||= Rack::Response.new
38
+ end
39
+
40
+ def status(value = nil)
41
+ response.status ||= 200
42
+ value ? response.status = value : response.status
43
+ end
44
+
45
+ def halt(new_status, body = '', new_headers = {})
46
+ status new_status
47
+ response.headers.merge!(new_headers)
48
+ # p response.body
49
+ if body.empty? &&
50
+ !Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)
51
+ body = Rack::Utils::HTTP_STATUS_CODES[status]
52
+ end
53
+ throw :halt, body
54
+ end
55
+
56
+ def path_to(ctrl, action, args = {})
57
+ route = @app.router.find_route(controller: ctrl, action: action)
58
+ fail RouteNotFoundError.new(ctrl, action) unless route
59
+ route.assign_arguments(args)
60
+ end
61
+
62
+ private
63
+
64
+ def try_route
65
+ method = params['_method'] || request.request_method
66
+ path = request.path_info
67
+ route = @app.router.find_route(
68
+ method: method,
69
+ path: path
70
+ )
71
+ # p route
72
+ return nil unless route
73
+ status 200
74
+ route.execute(self)
75
+ end
76
+
77
+ def try_static(dir = config[:public_dir])
78
+ static_file = File.join(dir, request.path_info)
79
+ # p static_file
80
+ return nil unless File.exist?(static_file) && File.file?(static_file)
81
+ return_static(static_file)
82
+ end
83
+
84
+ def return_static(file)
85
+ since = @env['HTTP_IF_MODIFIED_SINCE']
86
+ file_time = File.mtime(file)
87
+ halt 304 if since && Time.httpdate(since).to_i >= file_time.to_i
88
+ response.headers.merge!(
89
+ 'Content-Type' => file_mime_type(file),
90
+ 'Last-Modified' => file_time.httpdate
91
+ # 'Content-Disposition' => 'attachment;' \
92
+ # "filename=\"#{File.basename(static_file)}\"",
93
+ # 'Content-Length' => File.size?(static_file).to_s
94
+ )
95
+ halt 200, File.read(file)
96
+ end
97
+
98
+ def file_mime_type(file)
99
+ Rack::Mime.mime_type(File.extname(file))
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,80 @@
1
+ module Flame
2
+ module RouterError
3
+ ## Error for Flame::Router.compare_actions
4
+ class ActionsError < StandardError
5
+ def initialize(ctrl, extra_actions)
6
+ @ctrl = ctrl
7
+ @extra_actions = extra_actions
8
+ end
9
+ end
10
+
11
+ ## Error if routes have more actions, than controller
12
+ class ExtraRoutesActionsError < ActionsError
13
+ def message
14
+ "Controller '#{@ctrl}' has no methods" \
15
+ " '#{@extra_actions.join(', ')}' from routes"
16
+ end
17
+ end
18
+
19
+ ## Error if controller has not assigned in routes actions
20
+ class ExtraControllerActionsError < ActionsError
21
+ def message
22
+ "Routes for '#{@ctrl}' has no methods" \
23
+ " '#{@extra_actions.join(', ')}'"
24
+ end
25
+ end
26
+
27
+ ## Error for Flame::Router::RouteRefine.arguments_valid?
28
+ class ArgumentsError < StandardError
29
+ def initialize(ctrl, action, path, extra_args)
30
+ @ctrl = ctrl
31
+ @action = action
32
+ @path = path
33
+ @extra_args = extra_args
34
+ end
35
+ end
36
+
37
+ ## Error if path has more arguments, than controller's method
38
+ class ExtraPathArgumentsError < ArgumentsError
39
+ def message
40
+ "Method '#{@action}' from controller '#{@ctrl}'" \
41
+ " does not know arguments '#{@extra_args.join(', ')}'" \
42
+ " from path '#{@path}'"
43
+ end
44
+ end
45
+
46
+ ## Error if path has no arguments, that controller's method required
47
+ class ExtraActionArgumentsError < ArgumentsError
48
+ def message
49
+ "Path '#{@path}' does not contain required arguments" \
50
+ " '#{@extra_args.join(', ')}' of method '#{@action}'" \
51
+ " from controller '#{@ctrl}'"
52
+ end
53
+ end
54
+ end
55
+
56
+ ## Error for Flame::Router.find_path
57
+ class RouteNotFoundError < StandardError
58
+ def initialize(ctrl, method)
59
+ @ctrl = ctrl
60
+ @method = method
61
+ end
62
+
63
+ def message
64
+ "Route with controller '#{@ctrl}' and method '#{@method}'" \
65
+ ' not found in application routes'
66
+ end
67
+ end
68
+
69
+ ## Error for Flame::Controller.path_to
70
+ class ArgumentNotAssigned < StandardError
71
+ def initialize(path, path_part)
72
+ @path = path
73
+ @path_part = path_part
74
+ end
75
+
76
+ def message
77
+ "Argument '#{@path_part}' for '#{@path}' is not assigned"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,49 @@
1
+ require 'tilt'
2
+
3
+ module Flame
4
+ ## Helper for render functionality
5
+ module Render
6
+ def view(path, options = {})
7
+ ## Take options for rendering
8
+ scope = options.delete(:scope) || self
9
+ layout = options.delete(:layout) || 'layout.*'
10
+ ## And get the rest variables to locals
11
+ locals = options.merge(options.delete(:locals) || {})
12
+ ## Find filename
13
+ filename = find_file(path)
14
+ ## Compile Tilt to instance hash
15
+ @tilts ||= {}
16
+ @tilts[filename] ||= Tilt.new(filename)
17
+ ## Render Tilt from instance hash with new options
18
+ layout_render layout, @tilts[filename].render(scope, locals)
19
+ end
20
+
21
+ alias_method :render, :view
22
+
23
+ private
24
+
25
+ ## TODO: Add `views_dir` for Application and Controller
26
+ ## TODO: Add `layout` method for Controller
27
+ def find_file(path)
28
+ ## Build controller_dir
29
+ controller_dir = (
30
+ self.class.name.split(/(?=[A-Z])/) - ['Controller']
31
+ ).join('_').downcase
32
+ ## Get full filename
33
+ Dir[File.join(
34
+ config[:views_dir],
35
+ "{#{controller_dir},}",
36
+ "#{path}.*"
37
+ )].find do |file|
38
+ Tilt[file]
39
+ end
40
+ end
41
+
42
+ def layout_render(layout, result)
43
+ layout_filename = find_file(layout)
44
+ ## Compile layout to hash
45
+ @tilts[layout_filename] ||= Tilt.new(layout_filename)
46
+ @tilts[layout_filename] ? @tilts[layout_filename].render { result } : result
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,114 @@
1
+ module Flame
2
+ ## Class for Route in Router.routes
3
+ class Route
4
+ attr_reader :attributes
5
+
6
+ def initialize(attrs = {})
7
+ @attributes = attrs
8
+ end
9
+
10
+ def [](attribute)
11
+ @attributes[attribute]
12
+ end
13
+
14
+ ## Arguments in order as parameters of method of controller
15
+ def arrange_arguments(args = {})
16
+ self[:controller].instance_method(self[:action]).parameters
17
+ .each_with_object([]) do |par, arr|
18
+ arr << args[par[1]] if par[0] == :req || args[par[1]]
19
+ end
20
+ end
21
+
22
+ ## Compare attributes for `Router.find_route`
23
+ def compare_attributes(attrs)
24
+ attrs.each do |name, value|
25
+ next true if compare_attribute(name, value)
26
+ break false
27
+ end
28
+ end
29
+
30
+ ## Assign arguments to path for `Controller.path_to`
31
+ def assign_arguments(args = {})
32
+ extract_path_parts
33
+ .map { |path_part| assign_argument(path_part, args) }
34
+ .unshift('').join('/').gsub(%r{\/{2,}}, '/')
35
+ end
36
+
37
+ ## Execute by Application.call
38
+ def execute(app)
39
+ ctrl = self[:controller].new(app)
40
+ args = extract_arguments(app.request.path_info)
41
+ app.params.merge!(args)
42
+ ctrl.send(self[:action], *arrange_arguments(args))
43
+ end
44
+
45
+ private
46
+
47
+ ## Helpers for attributes accessors
48
+ def extract_path_parts(other_path = self[:path])
49
+ other_path.to_s.split('/').reject(&:empty?)
50
+ end
51
+
52
+ ## Extract arguments from request_path for `execute`
53
+ def extract_arguments(request_path)
54
+ args = {}
55
+ request_parts = extract_path_parts(request_path)
56
+ extract_path_parts.each_with_index do |path_part, i|
57
+ next unless path_part[0] == ':'
58
+ args[
59
+ path_part[(path_part[1] == '?' ? 2 : 1)..-1].to_sym
60
+ ] = URI.decode(request_parts[i])
61
+ end
62
+ args
63
+ end
64
+
65
+ ## Helpers for `compare_attributes`
66
+ def compare_attribute(name, value)
67
+ case name
68
+ when :method
69
+ compare_method(value)
70
+ when :path
71
+ compare_path(value)
72
+ else
73
+ self[name] == value
74
+ end
75
+ end
76
+
77
+ def compare_method(request_method)
78
+ self[:method].upcase.to_sym == request_method.upcase.to_sym
79
+ end
80
+
81
+ def compare_path(request_path)
82
+ path_parts = extract_path_parts
83
+ request_parts = extract_path_parts(request_path)
84
+ # p route_path
85
+ req_path_parts = path_parts.select { |part| part[1] != '?' }
86
+ return false if request_parts.count < req_path_parts.count
87
+ compare_parts(request_parts, path_parts)
88
+ end
89
+
90
+ def compare_parts(request_parts, path_parts)
91
+ request_parts.each_with_index do |request_part, i|
92
+ path_part = path_parts[i]
93
+ # p request_part, path_part
94
+ break false unless path_part
95
+ next if path_part[0] == ':'
96
+ break false unless request_part == path_part
97
+ end
98
+ end
99
+
100
+ ## Helpers for `assign_arguments`
101
+ def assign_argument(path_part, args = {})
102
+ ## Not argument
103
+ return path_part unless path_part[0] == ':'
104
+ ## Not required argument
105
+ return args[path_part[2..-1].to_sym] if path_part[1] == '?'
106
+ ## Required argument
107
+ param = args[path_part[1..-1].to_sym]
108
+ ## Required argument is nil
109
+ fail ArgumentNotAssigned.new(self[:path], path_part) if param.nil?
110
+ ## All is ok
111
+ param
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,126 @@
1
+ require_relative './route.rb'
2
+ require_relative './validators.rb'
3
+
4
+ module Flame
5
+ ## Router class for routing
6
+ class Router
7
+ attr_accessor :routes
8
+
9
+ def initialize
10
+ @routes = []
11
+ end
12
+
13
+ def add_controller(ctrl, path, block = nil)
14
+ ## TODO: Add Regexp paths
15
+ ## TODO: Add `before` and `after` methods
16
+
17
+ ## Add routes from controller to glob array
18
+ ctrl_routes = RouteRefine.new(ctrl, path, block).routes
19
+ ActionsValidator.new(ctrl_routes, ctrl).valid?
20
+ routes.concat(ctrl_routes)
21
+ end
22
+
23
+ ## Find route by any attributes
24
+ def find_route(attrs)
25
+ routes.find { |route| route.compare_attributes(attrs) }
26
+ end
27
+
28
+ private
29
+
30
+ ## Helper module for routing refine
31
+ class RouteRefine
32
+ attr_accessor :rest_routes
33
+ attr_reader :routes
34
+
35
+ def self.http_methods
36
+ [:GET, :POST, :PUT, :DELETE]
37
+ end
38
+
39
+ def rest_routes
40
+ @rest_routes ||= [
41
+ { method: :GET, path: '/', action: :index },
42
+ { method: :POST, path: '/', action: :create },
43
+ { method: :GET, path: '/', action: :show },
44
+ { method: :PUT, path: '/', action: :update },
45
+ { method: :DELETE, path: '/', action: :delete }
46
+ ]
47
+ end
48
+
49
+ def initialize(ctrl, path, block)
50
+ @ctrl = ctrl
51
+ @path = path || default_controller_path
52
+ @routes = []
53
+ block.nil? ? defaults : instance_exec(&block)
54
+ # p @routes
55
+ @routes.sort! { |a, b| b[:path] <=> a[:path] }
56
+ end
57
+
58
+ http_methods.each do |request_method|
59
+ define_method(request_method.downcase) do |path, action|
60
+ ArgumentsValidator.new(@ctrl, path, action).valid?
61
+ add_route(request_method, path, action)
62
+ end
63
+ end
64
+
65
+ def defaults
66
+ @ctrl.public_instance_methods(false).each do |action|
67
+ next if find_route_index(action: action)
68
+ add_route(:GET, nil, action)
69
+ end
70
+ end
71
+
72
+ def rest
73
+ rest_routes.each do |rest_route|
74
+ action = rest_route[:action]
75
+ if @ctrl.public_instance_methods.include?(action) &&
76
+ find_route_index(action: action).nil?
77
+ add_route(*rest_route.values, true)
78
+ end
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ using GorillaPatch::StringExt
85
+
86
+ def default_controller_path
87
+ @ctrl.name.underscore
88
+ .split('_')
89
+ .take_while { |part| part != 'controller' }
90
+ .unshift(nil)
91
+ .join('/')
92
+ end
93
+
94
+ def make_path(path, action = nil, force_params = false)
95
+ ## TODO: Add :arg:type support (:id:num, :name:str, etc.)
96
+ unshifted = force_params ? path : action_path(action)
97
+ if path.nil? || force_params
98
+ path = @ctrl.instance_method(action).parameters
99
+ .map { |par| ":#{par[0] == :req ? '' : '?'}#{par[1]}" }
100
+ .unshift(unshifted)
101
+ .join('/')
102
+ end
103
+ "#{@path}/#{path}".gsub(%r{\/{2,}}, '/')
104
+ end
105
+
106
+ def action_path(action)
107
+ action == :index ? '/' : action
108
+ end
109
+
110
+ def add_route(method, path, action, force_params = false)
111
+ route = Route.new(
112
+ method: method,
113
+ path: make_path(path, action, force_params),
114
+ controller: @ctrl,
115
+ action: action
116
+ )
117
+ index = find_route_index(action: action)
118
+ index ? @routes[index] = route : @routes.push(route)
119
+ end
120
+
121
+ def find_route_index(attrs)
122
+ @routes.find_index { |route| route.compare_attributes(attrs) }
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,89 @@
1
+ require_relative './errors.rb'
2
+
3
+ module Flame
4
+ ## Compare arguments from path and from controller's action
5
+ class ArgumentsValidator
6
+ def initialize(ctrl, path, action)
7
+ @ctrl = ctrl
8
+ @path = path
9
+ @action = action
10
+ end
11
+
12
+ def valid?
13
+ ## Break path for ':arg' arguments
14
+ @path_args = path_arguments(@path)
15
+ ## Take all and required arguments from Controller#action
16
+ @action_args = action_arguments(@action)
17
+ ## Compare arguments from path and arguments from method
18
+ no_extra_path_arguments? && no_extra_action_arguments?
19
+ end
20
+
21
+ private
22
+
23
+ ## Split path to args array
24
+ def path_arguments(path)
25
+ args = path.split('/').select { |part| part[0] == ':' }
26
+ args.map { |arg| arg[1..-1].to_sym }
27
+ end
28
+
29
+ ## Take args from controller's action
30
+ def action_arguments(action)
31
+ parameters = @ctrl.instance_method(action).parameters
32
+ req_parameters = parameters.select { |par| par[0] == :req }
33
+ {
34
+ all: parameters.map { |par| par[1] },
35
+ req: req_parameters.map { |par| par[1] }
36
+ }
37
+ end
38
+
39
+ def no_extra_path_arguments?
40
+ ## Subtraction action args from path args
41
+ extra_path_args = @path_args - @action_args[:all]
42
+ return true if extra_path_args.empty?
43
+ fail RouterError::ExtraPathArgumentsError.new(
44
+ @ctrl, @action, @path, extra_path_args
45
+ )
46
+ end
47
+
48
+ def no_extra_action_arguments?
49
+ ## Subtraction path args from action required args
50
+ extra_action_args = @action_args[:req] - @path_args
51
+ return true if extra_action_args.empty?
52
+ fail RouterError::ExtraActionArgumentsError.new(
53
+ @ctrl, @action, @path, extra_action_args
54
+ )
55
+ end
56
+ end
57
+
58
+ ## Compare actions from routes and from controller
59
+ class ActionsValidator
60
+ def initialize(routes, ctrl)
61
+ @routes = routes
62
+ @ctrl = ctrl
63
+ end
64
+
65
+ def valid?
66
+ @routes_actions = @routes.map { |route| route[:action] }
67
+ @ctrl_actions = @ctrl.public_instance_methods(false)
68
+ no_extra_routes_actions? && no_extra_controller_actions?
69
+ end
70
+
71
+ private
72
+
73
+ def no_extra_routes_actions?
74
+ extra_routes_actions = @routes_actions - @ctrl_actions
75
+ return true if extra_routes_actions.empty?
76
+ fail RouterError::ExtraRoutesActionsError.new(
77
+ @ctrl, extra_routes_actions
78
+ )
79
+ end
80
+
81
+ def no_extra_controller_actions?
82
+ extra_ctrl_actions = @ctrl_actions - @routes_actions
83
+ return true if extra_ctrl_actions.empty?
84
+ fail RouterError::ExtraControllerActionsError.new(
85
+ @ctrl, extra_ctrl_actions
86
+ )
87
+ end
88
+ end
89
+ end
Binary file
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flame
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Alexander Popov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.6.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.6'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.6.4
33
+ - !ruby/object:Gem::Dependency
34
+ name: gorilla-patch
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.0'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.0.5.3
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.0'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.0.5.3
53
+ description: Use controller's classes with instance methods as routing actions, mounting
54
+ its in application class.
55
+ email:
56
+ - alex.wayfer@gmail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - lib/flame.rb
62
+ - lib/flame/application.rb
63
+ - lib/flame/controller.rb
64
+ - lib/flame/dispatcher.rb
65
+ - lib/flame/errors.rb
66
+ - lib/flame/render.rb
67
+ - lib/flame/route.rb
68
+ - lib/flame/router.rb
69
+ - lib/flame/validators.rb
70
+ - public/favicon.ico
71
+ homepage: https://gitlab.com/AlexWayfer/flame
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 2.4.5.1
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Web-framework, based on MVC-pattern
95
+ test_files: []