a_little_less 0.2.1

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.
@@ -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: []