a_little_less 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +10 -0
- data/_config.yml +1 -0
- data/a_little_less.gemspec +33 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/a_little_less.rb +147 -0
- data/lib/a_little_less/core_ext.rb +102 -0
- data/lib/a_little_less/env.rb +25 -0
- data/lib/a_little_less/global.rb +9 -0
- data/lib/a_little_less/global_logger.rb +69 -0
- data/lib/a_little_less/rack_app.rb +214 -0
- data/lib/a_little_less/router.rb +66 -0
- data/lib/a_little_less/util.rb +64 -0
- data/lib/a_little_less/version.rb +3 -0
- data/lib/vendor/rails_blank.rb +133 -0
- data/lib/vendor/rails_inclusion.rb +27 -0
- data/sample_app/.gitignore +2 -0
- data/sample_app/.ruby-version +1 -0
- data/sample_app/Gemfile +13 -0
- data/sample_app/app/controllers/sample.rb +21 -0
- data/sample_app/app/models/user.rb +13 -0
- data/sample_app/config.ru +21 -0
- data/sample_app/config/db.yml +6 -0
- data/sample_app/console +4 -0
- data/sample_app/lib/my_lib/my_lib.rb +3 -0
- data/sample_app/lib/my_other_lib.rb +3 -0
- data/sample_app/public/.keep +0 -0
- data/sample_app/public/index.html +0 -0
- metadata +187 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 78b9df128c2de9afe5b00581080345c962ad84d0
|
4
|
+
data.tar.gz: ba48dffa35cbb68ba9ebf5ffadaf880ce22b370d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0c7cf9afae7d2dfd02e740d30fb82e42326bc1b3472c150f99cdffe40b8f4a9126b6bf35b96978afe5a881b8fd1b0af03a39bf17587a3e9a6dc9ea6d3dd804b3
|
7
|
+
data.tar.gz: 8d5968c8c2fb959b41eef38c6a854c74ff7b8ac373c9d14dc6e9cfd22cf6f63f6aca8548bf6226a2647f40e774ca0434ca1187a13f4ca75ecba809ebb73e5f74
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Tom Lobato
|
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,39 @@
|
|
1
|
+
# ALittleLess
|
2
|
+
|
3
|
+
A web framework a little more simple.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'a_little_less'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install a_little_less
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
See the [/sample_app](https://github.com/tomlobato/little_less/tree/master/sample_app).
|
24
|
+
|
25
|
+
## Development
|
26
|
+
|
27
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
28
|
+
|
29
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/a_little_less.
|
34
|
+
|
35
|
+
|
36
|
+
## License
|
37
|
+
|
38
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
39
|
+
|
data/Rakefile
ADDED
data/_config.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme: jekyll-theme-cayman
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'a_little_less/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "a_little_less"
|
8
|
+
spec.version = ALittleLess::VERSION
|
9
|
+
spec.authors = ["Tom Lobato"]
|
10
|
+
|
11
|
+
spec.summary = 'Basic web framework in Ruby'
|
12
|
+
spec.description = 'Basic web framework in Ruby.'
|
13
|
+
spec.homepage = "https://tomlobato.github.io/a_little_less"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
26
|
+
|
27
|
+
spec.add_runtime_dependency 'rack', '~> 2.0'
|
28
|
+
spec.add_runtime_dependency 'i18n', '~> 0.7'
|
29
|
+
spec.add_runtime_dependency 'colorize', '~> 0.8'
|
30
|
+
spec.add_runtime_dependency 'activerecord', '~> 5.0.2'
|
31
|
+
spec.add_runtime_dependency 'bugsnag', '~> 5.0'
|
32
|
+
end
|
33
|
+
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "a_little_less"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require "a_little_less/version"
|
2
|
+
|
3
|
+
APP_ROOT = File.expand_path '.'
|
4
|
+
|
5
|
+
require 'logger'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'yaml'
|
8
|
+
require 'json'
|
9
|
+
require 'i18n'
|
10
|
+
require 'singleton'
|
11
|
+
require 'colorize'
|
12
|
+
require 'active_record'
|
13
|
+
|
14
|
+
class AlittleLess; end
|
15
|
+
|
16
|
+
require_relative 'vendor/rails_blank'
|
17
|
+
require_relative 'vendor/rails_inclusion'
|
18
|
+
require_relative 'a_little_less/global'
|
19
|
+
require_relative 'a_little_less/router'
|
20
|
+
require_relative 'a_little_less/util'
|
21
|
+
require_relative 'a_little_less/core_ext'
|
22
|
+
require_relative 'a_little_less/global_logger'
|
23
|
+
require_relative 'a_little_less/env'
|
24
|
+
require_relative 'a_little_less/rack_app'
|
25
|
+
|
26
|
+
class AlittleLess
|
27
|
+
DB_CONF = "config/db.yml"
|
28
|
+
include AlittleLess::Util
|
29
|
+
include AlittleLess::Router
|
30
|
+
|
31
|
+
# Routing
|
32
|
+
|
33
|
+
@@controllers = {}
|
34
|
+
@@alias_name_map = {}
|
35
|
+
@@default_controller = nil
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def controllers
|
39
|
+
@@controllers
|
40
|
+
end
|
41
|
+
def alias_name_map
|
42
|
+
@@alias_name_map
|
43
|
+
end
|
44
|
+
def inherited subclass
|
45
|
+
controllers[subclass.to_s.decamelize] = {
|
46
|
+
get: {
|
47
|
+
# action_name string => action proc
|
48
|
+
},
|
49
|
+
head: {}, # head is the same as get, but doesn`t return body
|
50
|
+
post: {},
|
51
|
+
patch: {},
|
52
|
+
put: {},
|
53
|
+
delete: {}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
def default_controller
|
57
|
+
@@default_controller = self.to_s.decamelize
|
58
|
+
end
|
59
|
+
def get_default_controller
|
60
|
+
@@default_controller
|
61
|
+
end
|
62
|
+
def alias_name name
|
63
|
+
name = name.to_s
|
64
|
+
main_name = self.to_s.decamelize
|
65
|
+
if name != main_name
|
66
|
+
alias_name_map[name] = main_name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def add_action verb, action, block
|
70
|
+
controllers[ self.to_s.decamelize ][ verb ][ action.to_s ] = block
|
71
|
+
end
|
72
|
+
def get action, █ add_action __method__, action, block; end
|
73
|
+
def post action, █ add_action __method__, action, block; end
|
74
|
+
def patch action, █ add_action __method__, action, block; end
|
75
|
+
def put action, █ add_action __method__, action, block; end
|
76
|
+
def delete action, █ add_action __method__, action, block; end
|
77
|
+
def head action, █ add_action __method__, action, block; end
|
78
|
+
end
|
79
|
+
|
80
|
+
# RackApp entry point
|
81
|
+
|
82
|
+
def initialize req
|
83
|
+
@req = req
|
84
|
+
end
|
85
|
+
|
86
|
+
def conversation!
|
87
|
+
if http_options? and http_origin_allowed?
|
88
|
+
set_options_response
|
89
|
+
add_default_cors_headers
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
if route = search_route
|
94
|
+
# logi "route found: #{route.klass} #{route.action_proc}"
|
95
|
+
action_please! route
|
96
|
+
add_default_cors_headers
|
97
|
+
else
|
98
|
+
# logi 'route not found'
|
99
|
+
not_found
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private # the action always happens in private
|
104
|
+
|
105
|
+
def action_please! route
|
106
|
+
cont = route.klass.new @req
|
107
|
+
proc = route.action_proc
|
108
|
+
|
109
|
+
@params = @req.post_parts || @req.params
|
110
|
+
body = cont.instance_eval &proc
|
111
|
+
|
112
|
+
@req.resp.body = body if @req.resp.body.nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
# General
|
116
|
+
|
117
|
+
def self.require_dir dir
|
118
|
+
Dir[APP_ROOT + "/#{ dir }/*.rb"].each do |file|
|
119
|
+
require file
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.setup
|
124
|
+
I18n.config.available_locales = :en
|
125
|
+
require_dir "app/controllers"
|
126
|
+
require_dir "app/models" if setup_db
|
127
|
+
if File.exists? "lib"
|
128
|
+
require_dir "lib"
|
129
|
+
require_dir "lib/*"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# DB
|
134
|
+
|
135
|
+
def self.setup_db
|
136
|
+
if File.exists? DB_CONF
|
137
|
+
db_conf = YAML.load_file DB_CONF
|
138
|
+
ActiveRecord::Base.configurations["db"] = db_conf[AlittleLess.env.name]
|
139
|
+
true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
setup
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
All = AlittleLess
|
@@ -0,0 +1,102 @@
|
|
1
|
+
|
2
|
+
class Exception
|
3
|
+
def log
|
4
|
+
loge to_s_long
|
5
|
+
end
|
6
|
+
def to_s_long
|
7
|
+
l = [message]
|
8
|
+
l += backtrace if backtrace
|
9
|
+
l.join "\n"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class String
|
14
|
+
def decamelize
|
15
|
+
scan(/[A-Z][a-z]*/).join("_").downcase
|
16
|
+
end
|
17
|
+
def camelize
|
18
|
+
split('_').map(&:capitalize).join
|
19
|
+
end
|
20
|
+
def light_parameterize
|
21
|
+
I18n.transliterate(self)
|
22
|
+
.downcase
|
23
|
+
.gsub(/[^a-z0-9]+/, '-')
|
24
|
+
.gsub(/-+/, '-')
|
25
|
+
.gsub(/^-|-$/, '')
|
26
|
+
rescue => e
|
27
|
+
logi "#{e.message} for '#{self}'"
|
28
|
+
logi e.backtrace.join("\n") if e.backtrace
|
29
|
+
self
|
30
|
+
end
|
31
|
+
def safe_utf8
|
32
|
+
self.tidy_bytes.split(//u).join.force_encoding 'UTF-8'
|
33
|
+
end
|
34
|
+
def urlparams_to_hash
|
35
|
+
hash = {}
|
36
|
+
|
37
|
+
return hash if blank?
|
38
|
+
|
39
|
+
split('&').each do |i|
|
40
|
+
k, v = i.split('=')
|
41
|
+
if k and v
|
42
|
+
hash[ k ] = v
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
hash
|
47
|
+
end
|
48
|
+
def remove_extension
|
49
|
+
self.sub /\..*$/, ''
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Hash
|
54
|
+
def symbolize_keys
|
55
|
+
if self.is_a?(Hash)
|
56
|
+
Hash[ self.map { |k,v| [k.respond_to?(:to_sym) ? k.to_sym : k, v.is_a?(Hash) ? v.symbolize_keys : v] } ]
|
57
|
+
else
|
58
|
+
self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Dir
|
64
|
+
def self.ensure dir
|
65
|
+
unless File.exist? dir
|
66
|
+
FileUtils.mkdir_p dir
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Array
|
72
|
+
def all_present?
|
73
|
+
self.each do |i|
|
74
|
+
return false if i.blank?
|
75
|
+
end
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def any_present?
|
80
|
+
self.each do |i|
|
81
|
+
return true if i.present?
|
82
|
+
end
|
83
|
+
false
|
84
|
+
end
|
85
|
+
|
86
|
+
def any_blank?
|
87
|
+
self.each do |i|
|
88
|
+
return true if i.blank?
|
89
|
+
end
|
90
|
+
false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Pathname
|
95
|
+
def realpath_safe
|
96
|
+
realpath
|
97
|
+
rescue Errno::ENOENT => e
|
98
|
+
puts "Error following #{self}: #{e.message}"
|
99
|
+
puts e.backtrace.join("\n") if e.backtrace
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class AlittleLess::Env
|
2
|
+
DEFAULT_ENV = 'production'
|
3
|
+
def initialize env
|
4
|
+
@env = env.presence || DEFAULT_ENV
|
5
|
+
end
|
6
|
+
def prod?
|
7
|
+
@env == 'production'
|
8
|
+
end
|
9
|
+
def dev?
|
10
|
+
!prod?
|
11
|
+
end
|
12
|
+
def name
|
13
|
+
if prod?
|
14
|
+
"production"
|
15
|
+
else
|
16
|
+
"development"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class AlittleLess
|
22
|
+
def self.env
|
23
|
+
@all_env ||= Env.new ENV['ALL_ENV']
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class GlobalLogger
|
2
|
+
include Singleton
|
3
|
+
|
4
|
+
SHOW_DIV = false
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@logger = build_logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def log level, *args
|
11
|
+
@level = level
|
12
|
+
@caller = caller[1]
|
13
|
+
div if SHOW_DIV
|
14
|
+
args.each{|line| log_line line }
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def build_logger
|
21
|
+
logger = Logger.new log_file
|
22
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
23
|
+
"#{ severity[0] } #{ datetime.strftime('%y%m%d %H:%M:%S.%L') } #{ msg }\n"
|
24
|
+
end
|
25
|
+
logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def _log s
|
29
|
+
@logger.send @level, s
|
30
|
+
end
|
31
|
+
|
32
|
+
def div
|
33
|
+
_log "\033[#{1};31;#{40}m -----#{ @caller }---------------\033[0m"
|
34
|
+
end
|
35
|
+
|
36
|
+
def log_line line
|
37
|
+
_log output(line)
|
38
|
+
_log "----" if SHOW_DIV
|
39
|
+
end
|
40
|
+
|
41
|
+
def output line
|
42
|
+
if line.is_a?(Numeric) || line.is_a?(String) || line.is_a?(Symbol)
|
43
|
+
line
|
44
|
+
elsif line.is_a? Exception
|
45
|
+
line.to_s_long
|
46
|
+
else
|
47
|
+
line.to_yaml
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def log_file
|
52
|
+
fname = case ENV['ALL_ENV']
|
53
|
+
when 'PRODUCTION'
|
54
|
+
'production'
|
55
|
+
else
|
56
|
+
'development'
|
57
|
+
end
|
58
|
+
"log/#{ fname }.log"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def logd *args; GlobalLogger.instance.log :debug, *args; end
|
63
|
+
def logi *args; GlobalLogger.instance.log :info, *args; end
|
64
|
+
def logw *args; GlobalLogger.instance.log :warn, *args; end
|
65
|
+
def loge *args; GlobalLogger.instance.log :error, *args; end
|
66
|
+
def logf *args; GlobalLogger.instance.log :fatal, *args; end
|
67
|
+
def logu *args; GlobalLogger.instance.log :unknown, *args; end
|
68
|
+
alias log logi
|
69
|
+
|
@@ -0,0 +1,214 @@
|
|
1
|
+
class AlittleLess::RackApp
|
2
|
+
SLOW_GET_THRESOLD = 0.2
|
3
|
+
|
4
|
+
ID_CHARS = 'a'.upto('z').to_a +
|
5
|
+
'A'.upto('Z').to_a +
|
6
|
+
'0'.upto('9').to_a
|
7
|
+
|
8
|
+
def self.call env
|
9
|
+
new(env).call
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize env
|
13
|
+
@env = env
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
req = build_req
|
18
|
+
req.id = rand_id
|
19
|
+
resp = req.resp
|
20
|
+
|
21
|
+
t0 = Time.now
|
22
|
+
run_safe { log_req req }
|
23
|
+
|
24
|
+
begin
|
25
|
+
AlittleLessApp.new(req).conversation!
|
26
|
+
rescue => e
|
27
|
+
loge e
|
28
|
+
Bugsnag.notify e
|
29
|
+
resp.status = 500
|
30
|
+
end
|
31
|
+
|
32
|
+
resp.headers['X-Runtime'] = Time.now - t0
|
33
|
+
run_safe { log_resp req }
|
34
|
+
handle_body req, resp
|
35
|
+
|
36
|
+
[
|
37
|
+
resp.status,
|
38
|
+
resp.headers,
|
39
|
+
resp.body
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def rand_id
|
46
|
+
4.times.map{ ID_CHARS.sample }.join
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_body req, resp
|
50
|
+
h = resp.headers
|
51
|
+
|
52
|
+
if resp.body.is_a?(Hash) or resp.body.is_a?(Array)
|
53
|
+
h['Content-Type'] = 'application/json'
|
54
|
+
resp.body = [resp.body.to_json]
|
55
|
+
end
|
56
|
+
|
57
|
+
if p = h['X-Sendfile'].presence and h['Content-Type'].blank?
|
58
|
+
if c = get_content_type(p)
|
59
|
+
h['Content-Type'] = c
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_content_type path
|
65
|
+
return unless File.exists? path
|
66
|
+
require 'filemagic'
|
67
|
+
unless defined? @@file_magic
|
68
|
+
@@file_magic = FileMagic.open(:mime)
|
69
|
+
end
|
70
|
+
content_type = @@file_magic.file path
|
71
|
+
if content_type =~ /^\S+\/\S+; charset=\S+$/i
|
72
|
+
content_type
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def debug?
|
77
|
+
#true
|
78
|
+
end
|
79
|
+
|
80
|
+
def log_req req
|
81
|
+
if debug?
|
82
|
+
logi "--ENV--\n#{ @env.to_yaml }\n--/ENV--"
|
83
|
+
end
|
84
|
+
logi [
|
85
|
+
"Q #{req.id}",
|
86
|
+
req.http_method.upcase,
|
87
|
+
req.uri
|
88
|
+
].join(" ")
|
89
|
+
end
|
90
|
+
|
91
|
+
def log_resp req
|
92
|
+
resp = req.resp
|
93
|
+
status = resp.status.to_i
|
94
|
+
success = status >= 200 && status < 400
|
95
|
+
|
96
|
+
file = if success
|
97
|
+
if status >= 300
|
98
|
+
resp.headers['Location']
|
99
|
+
else
|
100
|
+
resp.headers['X-Sendfile'].to_s
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
status_str = resp.status.to_s
|
105
|
+
if success
|
106
|
+
status_str = status_str.send (status >= 300 ? :cyan : :green)
|
107
|
+
end
|
108
|
+
|
109
|
+
log_line = [
|
110
|
+
"A #{req.id}",
|
111
|
+
status_str,
|
112
|
+
log_runtime(resp.headers['X-Runtime'], req.http_method),
|
113
|
+
file
|
114
|
+
].join(" ")
|
115
|
+
|
116
|
+
if success
|
117
|
+
logi log_line
|
118
|
+
else
|
119
|
+
loge log_line.red
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def log_runtime xrt, met
|
124
|
+
rt = xrt.to_f
|
125
|
+
rts = '%.6f' % rt
|
126
|
+
color = :light_red if met == :get && rt > SLOW_GET_THRESOLD
|
127
|
+
color ? rts.colorize(color) : rts
|
128
|
+
end
|
129
|
+
|
130
|
+
def params
|
131
|
+
Rack::Utils
|
132
|
+
.parse_nested_query(@env['QUERY_STRING'])
|
133
|
+
.symbolize_keys
|
134
|
+
end
|
135
|
+
|
136
|
+
def clean_uri
|
137
|
+
uri = @env['REQUEST_URI']
|
138
|
+
uri.sub! /^(https?)?:\/\/[^\/]+/i, ''
|
139
|
+
Rack::Utils.unescape uri
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_req
|
143
|
+
OpenStruct.new(
|
144
|
+
http_method: @env['REQUEST_METHOD'].downcase.to_sym,
|
145
|
+
uri: clean_uri,
|
146
|
+
params: params,
|
147
|
+
env: @env,
|
148
|
+
resp: build_resp,
|
149
|
+
post_parts: Rack::Multipart.parse_multipart(@env),
|
150
|
+
referer: @env['HTTP_REFERER'],
|
151
|
+
ua: @env['HTTP_USER_AGENT']
|
152
|
+
# body: @env['rack.input'].tmp # for file uploads non multipart
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
def build_resp
|
157
|
+
OpenStruct.new(
|
158
|
+
status: 200,
|
159
|
+
headers: {},
|
160
|
+
body: nil
|
161
|
+
)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class AlittleLess
|
166
|
+
def self.rack_app
|
167
|
+
RackApp
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# Example @env:
|
173
|
+
|
174
|
+
# REQUEST_URI: "/files/e/4/7/6/a/c/4/e476ac4e683ea737ddad1e53127819b983f1d5a5.jpg?fill=210x210&type=square210"
|
175
|
+
# PATH_INFO: "/files/e/4/7/6/a/c/4/e476ac4e683ea737ddad1e53127819b983f1d5a5.jpg"
|
176
|
+
# SCRIPT_NAME: ''
|
177
|
+
# QUERY_STRING: fill=210x210&type=square210
|
178
|
+
# REQUEST_METHOD: GET
|
179
|
+
# SERVER_NAME: example.com
|
180
|
+
# SERVER_PORT: '443'
|
181
|
+
# SERVER_SOFTWARE: Apache
|
182
|
+
# SERVER_PROTOCOL: HTTP/1.1
|
183
|
+
# REMOTE_ADDR: 179.234.49.149
|
184
|
+
# REMOTE_PORT: '56323'
|
185
|
+
# HTTPS: 'on'
|
186
|
+
# HTTP_USER_AGENT: Mozilla/5.0 (Linux; U; Android 4.3; pt-br; SM-G3502T Build/JLS36C)
|
187
|
+
# AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30
|
188
|
+
# HTTP_ACCEPT_ENCODING: gzip,deflate
|
189
|
+
# HTTP_ACCEPT: "*/*"
|
190
|
+
# HTTP_ACCEPT_LANGUAGE: pt-BR, en-US
|
191
|
+
# HTTP_HOST: example.com
|
192
|
+
# HTTP_ACCEPT_CHARSET: utf-8, iso-8859-1, utf-16, *;q=0.7
|
193
|
+
# SSL_TLS_SNI: example.com
|
194
|
+
# rack.version:
|
195
|
+
# - 1
|
196
|
+
# - 2
|
197
|
+
# rack.input: !ruby/object:PhusionPassenger::Utils::TeeInput
|
198
|
+
# len: 0
|
199
|
+
# socket: !ruby/object:PhusionPassenger::Utils::UnseekableSocket
|
200
|
+
# socket: !ruby/object:UNIXSocket {}
|
201
|
+
# simulate_eof: true
|
202
|
+
# bytes_read: 0
|
203
|
+
# tmp: !ruby/object:StringIO {}
|
204
|
+
# rack.errors: !ruby/object:IO {}
|
205
|
+
# rack.multithread: false
|
206
|
+
# rack.multiprocess: true
|
207
|
+
# rack.run_once: false
|
208
|
+
# rack.url_scheme: https
|
209
|
+
# rack.hijack?: true
|
210
|
+
# rack.hijack: !ruby/object:Proc {}
|
211
|
+
# HTTP_VERSION: HTTP/1.1
|
212
|
+
# rack.tempfiles: []
|
213
|
+
|
214
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module AlittleLess::Router
|
2
|
+
def search_route
|
3
|
+
nodes = @req.uri
|
4
|
+
.sub(/\?.*/,'') # wipe query string
|
5
|
+
.split('/') # get path nodes
|
6
|
+
|
7
|
+
# get 2 first nodes after /
|
8
|
+
controller_name, action_name = nodes[1,2]
|
9
|
+
|
10
|
+
# Below, we try to match url path nodes to controllers
|
11
|
+
# and actions defined in @@controllers
|
12
|
+
#
|
13
|
+
# @@controllers estructure example, as defined by AlittleLess:
|
14
|
+
#
|
15
|
+
# controllers["users"] = {
|
16
|
+
# get: {
|
17
|
+
# "index" => #<Proc,
|
18
|
+
# "show" => #<Proc
|
19
|
+
# },
|
20
|
+
# post: {
|
21
|
+
# "create" => #<Proc
|
22
|
+
# }
|
23
|
+
# }
|
24
|
+
#
|
25
|
+
# Responding to:
|
26
|
+
# GET /users/index
|
27
|
+
# GET /users/show
|
28
|
+
# POST /users/create
|
29
|
+
|
30
|
+
# Resolve alias_name`s
|
31
|
+
if main_name = self.class.alias_name_map[controller_name]
|
32
|
+
controller_name = main_name
|
33
|
+
end
|
34
|
+
|
35
|
+
http_methods = self.class.controllers[controller_name]
|
36
|
+
|
37
|
+
# If no controllers found til here, lets check
|
38
|
+
# if there exists a default controller
|
39
|
+
unless http_methods
|
40
|
+
if controller_name = self.class.get_default_controller
|
41
|
+
http_methods = self.class.controllers[controller_name]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
return unless http_methods
|
46
|
+
|
47
|
+
actions = http_methods[method]
|
48
|
+
return unless actions
|
49
|
+
|
50
|
+
# Try to match the action
|
51
|
+
action_proc = actions[action_name] || actions['*']
|
52
|
+
return unless action_proc
|
53
|
+
|
54
|
+
OpenStruct.new(
|
55
|
+
klass: Object.const_get(controller_name.camelize),
|
56
|
+
action_proc: action_proc
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def method
|
61
|
+
@req.http_method
|
62
|
+
.to_s
|
63
|
+
.sub('head', 'get') # coalescing HEAD, as we only remove the body before responding
|
64
|
+
.to_sym
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module AlittleLess::Util
|
2
|
+
|
3
|
+
# Response
|
4
|
+
|
5
|
+
def params
|
6
|
+
@req.params
|
7
|
+
end
|
8
|
+
|
9
|
+
def send_file file
|
10
|
+
@req.resp.headers['X-Sendfile'] = file
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def status code
|
15
|
+
@req.resp.status = code
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def not_found
|
20
|
+
status 404
|
21
|
+
end
|
22
|
+
|
23
|
+
def with body
|
24
|
+
@req.resp.body = body
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def error_422 body
|
29
|
+
status 422
|
30
|
+
with body
|
31
|
+
end
|
32
|
+
|
33
|
+
def redir_301 url
|
34
|
+
@req.resp.headers['Location'] = url
|
35
|
+
@req.resp.status = 301
|
36
|
+
end
|
37
|
+
|
38
|
+
# HTTP CORS
|
39
|
+
|
40
|
+
def http_options?
|
41
|
+
@req.http_method == :options
|
42
|
+
end
|
43
|
+
|
44
|
+
def http_origin_allowed?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_default_cors_headers
|
49
|
+
@req.resp.headers.merge!(
|
50
|
+
'Access-Control-Allow-Origin' => '*',
|
51
|
+
'Access-Control-Allow-Methods' => 'POST, GET, OPTIONS',
|
52
|
+
'Access-Control-Max-Age' => '1728000'
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_options_response
|
57
|
+
@req.resp.headers.merge!(
|
58
|
+
'Access-Control-Allow-Headers' => 'Accept, Cache-Control, Content-Type, X-Requested-With',
|
59
|
+
'Content-Type' => 'text/plain'
|
60
|
+
)
|
61
|
+
@req.resp.body = ''
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Rails 4.2.4
|
4
|
+
|
5
|
+
class Object
|
6
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
7
|
+
# For example, '', ' ', +nil+, [], and {} are all blank.
|
8
|
+
#
|
9
|
+
# This simplifies
|
10
|
+
#
|
11
|
+
# address.nil? || address.empty?
|
12
|
+
#
|
13
|
+
# to
|
14
|
+
#
|
15
|
+
# address.blank?
|
16
|
+
#
|
17
|
+
# @return [true, false]
|
18
|
+
def blank?
|
19
|
+
respond_to?(:empty?) ? !!empty? : !self
|
20
|
+
end
|
21
|
+
|
22
|
+
# An object is present if it's not blank.
|
23
|
+
#
|
24
|
+
# @return [true, false]
|
25
|
+
def present?
|
26
|
+
!blank?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the receiver if it's present otherwise returns +nil+.
|
30
|
+
# <tt>object.presence</tt> is equivalent to
|
31
|
+
#
|
32
|
+
# object.present? ? object : nil
|
33
|
+
#
|
34
|
+
# For example, something like
|
35
|
+
#
|
36
|
+
# state = params[:state] if params[:state].present?
|
37
|
+
# country = params[:country] if params[:country].present?
|
38
|
+
# region = state || country || 'US'
|
39
|
+
#
|
40
|
+
# becomes
|
41
|
+
#
|
42
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
43
|
+
#
|
44
|
+
# @return [Object]
|
45
|
+
def presence
|
46
|
+
self if present?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class NilClass
|
51
|
+
# +nil+ is blank:
|
52
|
+
#
|
53
|
+
# nil.blank? # => true
|
54
|
+
#
|
55
|
+
# @return [true]
|
56
|
+
def blank?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class FalseClass
|
62
|
+
# +false+ is blank:
|
63
|
+
#
|
64
|
+
# false.blank? # => true
|
65
|
+
#
|
66
|
+
# @return [true]
|
67
|
+
def blank?
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class TrueClass
|
73
|
+
# +true+ is not blank:
|
74
|
+
#
|
75
|
+
# true.blank? # => false
|
76
|
+
#
|
77
|
+
# @return [false]
|
78
|
+
def blank?
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Array
|
84
|
+
# An array is blank if it's empty:
|
85
|
+
#
|
86
|
+
# [].blank? # => true
|
87
|
+
# [1,2,3].blank? # => false
|
88
|
+
#
|
89
|
+
# @return [true, false]
|
90
|
+
alias_method :blank?, :empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
class Hash
|
94
|
+
# A hash is blank if it's empty:
|
95
|
+
#
|
96
|
+
# {}.blank? # => true
|
97
|
+
# { key: 'value' }.blank? # => false
|
98
|
+
#
|
99
|
+
# @return [true, false]
|
100
|
+
alias_method :blank?, :empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
class String
|
104
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
105
|
+
|
106
|
+
# A string is blank if it's empty or contains whitespaces only:
|
107
|
+
#
|
108
|
+
# ''.blank? # => true
|
109
|
+
# ' '.blank? # => true
|
110
|
+
# "\t\n\r".blank? # => true
|
111
|
+
# ' blah '.blank? # => false
|
112
|
+
#
|
113
|
+
# Unicode whitespace is supported:
|
114
|
+
#
|
115
|
+
# "\u00a0".blank? # => true
|
116
|
+
#
|
117
|
+
# @return [true, false]
|
118
|
+
def blank?
|
119
|
+
BLANK_RE === self
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Numeric #:nodoc:
|
124
|
+
# No number is blank:
|
125
|
+
#
|
126
|
+
# 1.blank? # => false
|
127
|
+
# 0.blank? # => false
|
128
|
+
#
|
129
|
+
# @return [false]
|
130
|
+
def blank?
|
131
|
+
false
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Object
|
2
|
+
# Returns true if this object is included in the argument. Argument must be
|
3
|
+
# any object which responds to +#include?+. Usage:
|
4
|
+
#
|
5
|
+
# characters = ["Konata", "Kagami", "Tsukasa"]
|
6
|
+
# "Konata".in?(characters) # => true
|
7
|
+
#
|
8
|
+
# This will throw an ArgumentError if the argument doesn't respond
|
9
|
+
# to +#include?+.
|
10
|
+
def in?(another_object)
|
11
|
+
another_object.include?(self)
|
12
|
+
rescue NoMethodError
|
13
|
+
raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the receiver if it's included in the argument otherwise returns +nil+.
|
17
|
+
# Argument must be any object which responds to +#include?+. Usage:
|
18
|
+
#
|
19
|
+
# params[:bucket_type].presence_in %w( project calendar )
|
20
|
+
#
|
21
|
+
# This will throw an ArgumentError if the argument doesn't respond to +#include?+.
|
22
|
+
#
|
23
|
+
# @return [Object]
|
24
|
+
def presence_in(another_object)
|
25
|
+
self.in?(another_object) ? self : nil
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/sample_app/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
class Files < AlittleLess
|
3
|
+
default_controller
|
4
|
+
get '*' do
|
5
|
+
# Helpers
|
6
|
+
# send_file path
|
7
|
+
# not_found
|
8
|
+
redir_301 '/policy.html'
|
9
|
+
end
|
10
|
+
post 'upload' do
|
11
|
+
if DoSome.thing
|
12
|
+
break error_422 success: false
|
13
|
+
else
|
14
|
+
{success: true}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
get 'users' do
|
18
|
+
User.limit(10).map &:attributes
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
# Bugsnag
|
3
|
+
|
4
|
+
require "bugsnag"
|
5
|
+
|
6
|
+
Bugsnag.configure do |config|
|
7
|
+
config.api_key = "TODO: fill it with your api key"
|
8
|
+
config.project_root = '/var/www/yoursite'
|
9
|
+
end
|
10
|
+
|
11
|
+
use Bugsnag::Rack
|
12
|
+
|
13
|
+
# Timeout
|
14
|
+
|
15
|
+
#require "rack-timeout"
|
16
|
+
#use Rack::Timeout, service_timeout: 10
|
17
|
+
|
18
|
+
# Run
|
19
|
+
|
20
|
+
class AlittleLessApp < AlittleLess; end
|
21
|
+
run AlittleLessApp.rack_app
|
data/sample_app/console
ADDED
File without changes
|
File without changes
|
metadata
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: a_little_less
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Lobato
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rack
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: i18n
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.7'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.7'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: colorize
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.8'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.8'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activerecord
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 5.0.2
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 5.0.2
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: bugsnag
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '5.0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '5.0'
|
125
|
+
description: Basic web framework in Ruby.
|
126
|
+
email:
|
127
|
+
executables: []
|
128
|
+
extensions: []
|
129
|
+
extra_rdoc_files: []
|
130
|
+
files:
|
131
|
+
- ".gitignore"
|
132
|
+
- Gemfile
|
133
|
+
- LICENSE.txt
|
134
|
+
- README.md
|
135
|
+
- Rakefile
|
136
|
+
- _config.yml
|
137
|
+
- a_little_less.gemspec
|
138
|
+
- bin/console
|
139
|
+
- bin/setup
|
140
|
+
- lib/a_little_less.rb
|
141
|
+
- lib/a_little_less/core_ext.rb
|
142
|
+
- lib/a_little_less/env.rb
|
143
|
+
- lib/a_little_less/global.rb
|
144
|
+
- lib/a_little_less/global_logger.rb
|
145
|
+
- lib/a_little_less/rack_app.rb
|
146
|
+
- lib/a_little_less/router.rb
|
147
|
+
- lib/a_little_less/util.rb
|
148
|
+
- lib/a_little_less/version.rb
|
149
|
+
- lib/vendor/rails_blank.rb
|
150
|
+
- lib/vendor/rails_inclusion.rb
|
151
|
+
- sample_app/.gitignore
|
152
|
+
- sample_app/.ruby-version
|
153
|
+
- sample_app/Gemfile
|
154
|
+
- sample_app/app/controllers/sample.rb
|
155
|
+
- sample_app/app/models/user.rb
|
156
|
+
- sample_app/config.ru
|
157
|
+
- sample_app/config/db.yml
|
158
|
+
- sample_app/console
|
159
|
+
- sample_app/lib/my_lib/my_lib.rb
|
160
|
+
- sample_app/lib/my_other_lib.rb
|
161
|
+
- sample_app/public/.keep
|
162
|
+
- sample_app/public/index.html
|
163
|
+
homepage: https://tomlobato.github.io/a_little_less
|
164
|
+
licenses:
|
165
|
+
- MIT
|
166
|
+
metadata: {}
|
167
|
+
post_install_message:
|
168
|
+
rdoc_options: []
|
169
|
+
require_paths:
|
170
|
+
- lib
|
171
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
requirements: []
|
182
|
+
rubyforge_project:
|
183
|
+
rubygems_version: 2.5.1
|
184
|
+
signing_key:
|
185
|
+
specification_version: 4
|
186
|
+
summary: Basic web framework in Ruby
|
187
|
+
test_files: []
|