a_little_less 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in a_little_less.gemspec
4
+ gemspec
@@ -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.
@@ -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
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -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
+
@@ -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__)
@@ -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
@@ -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, &block; add_action __method__, action, block; end
73
+ def post action, &block; add_action __method__, action, block; end
74
+ def patch action, &block; add_action __method__, action, block; end
75
+ def put action, &block; add_action __method__, action, block; end
76
+ def delete action, &block; add_action __method__, action, block; end
77
+ def head action, &block; 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,9 @@
1
+
2
+ def run_safe
3
+ begin
4
+ yield
5
+ rescue => e
6
+ loge e
7
+ end
8
+ end
9
+
@@ -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,3 @@
1
+ module ALittleLess
2
+ VERSION = "0.2.1"
3
+ 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,2 @@
1
+ /log
2
+ /tmp
@@ -0,0 +1 @@
1
+ 2.3.1
@@ -0,0 +1,13 @@
1
+
2
+ source 'https://rubygems.org'
3
+
4
+ # framework
5
+ gem 'a_little_less'
6
+ gem 'mysql2'
7
+
8
+ # easy_img
9
+ gem 'addressable'
10
+ gem 'ruby-filemagic'
11
+ gem 'mime-types'
12
+ gem 'bugsnag'
13
+ gem 'httparty'
@@ -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,13 @@
1
+ class User < ActiveRecord::Base
2
+ establish_connection :db
3
+
4
+ attr_accessor :authenticated
5
+
6
+ def self.check_auth key, auth
7
+ user = User.find_by_user_hash key
8
+ return unless user
9
+ user.authenticated = auth == user.auth
10
+ user
11
+ end
12
+
13
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ production:
2
+ adapter: mysql2
3
+ database: db_name
4
+ username: user
5
+ password: pass
6
+ socket: /var/run/mysqld/mysqld.sock
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ irb -r alittle_less
4
+
@@ -0,0 +1,3 @@
1
+ class MyLib
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ class MyOtherLib
2
+
3
+ end
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: []