camping 2.1.532 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +72 -53
  3. data/Rakefile +25 -20
  4. data/bin/camping +1 -0
  5. data/book/01_introduction.md +6 -6
  6. data/book/02_getting_started.md +348 -267
  7. data/book/03_more_about_controllers.md +124 -0
  8. data/book/04_more_about_views.md +118 -0
  9. data/book/05_more_about_markaby.md +173 -0
  10. data/book/06_more_about_models.md +58 -0
  11. data/book/06_rules_of_thumb.md +143 -0
  12. data/book/07_philosophy.md +23 -0
  13. data/book/08_publishing_an_app.md +118 -0
  14. data/book/09_upgrade_notes.md +96 -0
  15. data/book/10_middleware.md +69 -0
  16. data/book/11_gear.md +50 -0
  17. data/examples/blog.rb +38 -38
  18. data/lib/camping/ar.rb +20 -5
  19. data/lib/camping/commands.rb +388 -0
  20. data/lib/camping/gear/filters.rb +48 -0
  21. data/lib/camping/gear/inspection.rb +32 -0
  22. data/lib/camping/gear/kuddly.rb +178 -0
  23. data/lib/camping/gear/nancy.rb +170 -0
  24. data/lib/camping/loads.rb +15 -0
  25. data/lib/camping/mab.rb +1 -1
  26. data/lib/camping/reloader.rb +22 -17
  27. data/lib/camping/server.rb +145 -70
  28. data/lib/camping/session.rb +8 -5
  29. data/lib/camping/template.rb +1 -2
  30. data/lib/camping/tools.rb +43 -0
  31. data/lib/camping/version.rb +6 -0
  32. data/lib/camping-unabridged.rb +360 -133
  33. data/lib/camping.rb +78 -47
  34. data/lib/campingtrip.md +341 -0
  35. data/test/app_camping_gear.rb +121 -0
  36. data/test/app_camping_tools.rb +1 -0
  37. data/test/app_config.rb +30 -0
  38. data/test/app_cookies.rb +1 -1
  39. data/test/app_file.rb +3 -3
  40. data/test/app_goes_meta.rb +23 -0
  41. data/test/app_inception.rb +39 -0
  42. data/test/app_markup.rb +5 -20
  43. data/test/app_migrations.rb +16 -0
  44. data/test/app_partials.rb +1 -1
  45. data/test/app_prefixed.rb +88 -0
  46. data/test/app_reloader.rb +1 -2
  47. data/test/app_route_generating.rb +69 -2
  48. data/test/app_sessions.rb +24 -2
  49. data/test/app_simple.rb +18 -18
  50. data/test/apps/migrations.rb +82 -82
  51. data/test/apps/misc.rb +1 -1
  52. data/test/gear/gear_nancy.rb +129 -0
  53. data/test/test_helper.rb +69 -12
  54. metadata +152 -92
  55. data/CHANGELOG +0 -145
  56. data/book/51_upgrading.md +0 -110
@@ -0,0 +1,388 @@
1
+ module Camping
2
+ module CommandsHelpers
3
+
4
+ # transform app_name to snake case
5
+ def self.to_snake_case(string)
6
+ string = string.to_s if string.class == Symbol
7
+ string.gsub(/::/, '/').
8
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
9
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
10
+ tr("-", "_").
11
+ downcase
12
+ end
13
+
14
+ RouteCollection = Struct.new(:routes)
15
+ class RouteCollection
16
+ # Displays formatted routes from a route collection
17
+ # Assumes that Route structs are stored in :routes.
18
+ def display
19
+ current_app, current_controller, current_method = "", "", ""
20
+ puts "App VERB Route"
21
+ routes.each { |r|
22
+ if current_app != r.app.to_s
23
+ current_app = r.app.to_s
24
+ puts "-----------------------------------"
25
+ puts r.app_header
26
+ end
27
+ if current_controller != r.controller.to_s
28
+ current_controller = r.controller.to_s
29
+ puts r.controller_header
30
+ end
31
+ puts r.padded_message true
32
+ }
33
+ end
34
+
35
+ end
36
+
37
+ # Route Struct, for making and formatting a route.
38
+ Route = Struct.new(:http_method, :controller, :app, :url)
39
+ class Route
40
+
41
+ def to_s
42
+ "#{controller}: #{http_method}: #{url} - #{replace_reg url}"
43
+ end
44
+
45
+ # pad the controller name to be the right length, if we can.
46
+ def padded_message(with_method = false)
47
+ "#{pad}#{(with_method ? http_method.to_s.upcase.ljust(pad.length, " ") : pad)}#{replace_reg url}"
48
+ end
49
+
50
+ def app_header
51
+ "#{app.to_s}"
52
+ end
53
+
54
+ def controller_header
55
+ "#{pad}#{app.to_s}::#{controller.to_s}"
56
+ end
57
+
58
+ protected
59
+
60
+ def http_methods
61
+ ["get", "post", "put", "patch", "delete"]
62
+ end
63
+
64
+ def replace_reg(pattern = "")
65
+ xstr = "([^/]+)"; nstr = "(\d+)"
66
+ pattern = pattern.gsub(xstr, ":string").gsub("(\\d+)", ":integer") unless pattern == "/"
67
+ pattern
68
+ end
69
+
70
+ def pad
71
+ " "
72
+ end
73
+
74
+ end
75
+
76
+ class RoutesParser
77
+ def self.parse(app)
78
+ new(app).parse
79
+ end
80
+
81
+ def initialize(app = Camping)
82
+ @parent_app, @routes = app, []
83
+ end
84
+
85
+ def parse
86
+ routes = @parent_app.make_camp
87
+ collected_routes = []
88
+
89
+ make_routes = -> (a) {
90
+
91
+ a::X.all.map {|c|
92
+ k = a::X.const_get(c)
93
+ im = k.instance_methods(false).map!(&:to_s)
94
+ methods = im & ["get", "post", "put", "patch", "delete"]
95
+ if k.respond_to?:urls
96
+ methods.each { |m|
97
+ k.urls.each { |u|
98
+ collected_routes.append Camping::CommandsHelpers::Route.new(m,c,a.to_s,u)
99
+ }
100
+ }
101
+ end
102
+ }
103
+ }
104
+
105
+ if @parent_app == Camping
106
+ @parent_app::Apps.each {|a|
107
+ make_routes.(a)
108
+ }
109
+ else
110
+ make_routes.(@parent_app)
111
+ end
112
+
113
+ routes_collection = Camping::CommandsHelpers::RouteCollection.new(collected_routes)
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ class Generators
120
+ class << self
121
+
122
+ # write a file
123
+ def write(file, content)
124
+ raise "Cannot write to nil file." unless file
125
+ folder = File.dirname(file)
126
+ `mkdir -p #{folder}` unless File.exist?(folder)
127
+ File.open(file, 'w') { |f| f.write content }
128
+ end
129
+
130
+ # read a file
131
+ def read(file)
132
+ File.read(file)
133
+ end
134
+
135
+ def make_camp_file(app_name="Tent")
136
+ write "camp.rb", <<-RUBY
137
+ require 'camping'
138
+
139
+ Camping.goes :#{app_name}
140
+
141
+ module #{app_name}
142
+ module Models
143
+ end
144
+
145
+ module Controllers
146
+ class Index
147
+ def get
148
+ @title = "#{app_name}"
149
+ render :index
150
+ end
151
+ end
152
+ end
153
+
154
+ module Helpers
155
+ end
156
+
157
+ module Views
158
+
159
+ def layout
160
+ html do
161
+ head do
162
+ title '#{app_name}'
163
+ link :rel => 'stylesheet', :type => 'text/css',
164
+ :href => '/styles.css', :media => 'screen'
165
+ end
166
+ body do
167
+ h1 '#{app_name}'
168
+
169
+ div.wrapper! do
170
+ self << yield
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ def index
177
+ h2 'Let\'s go Camping'
178
+ end
179
+
180
+ end
181
+
182
+ end
183
+
184
+ RUBY
185
+ end
186
+
187
+ # makes a gitignore.
188
+ def make_gitignore
189
+ write '.gitignore', <<-GIT
190
+ .DS_Store
191
+ node_modules/
192
+ tmp/
193
+ db/camping.db
194
+ db/camping.sqlite3
195
+ db/camping.sqlite
196
+ GIT
197
+ end
198
+
199
+ def make_ruby_version
200
+ write '.ruby-version', <<-RUBY
201
+ #{RUBY_VERSION}
202
+ RUBY
203
+ end
204
+
205
+ # writes a rakefile
206
+ def make_rakefile
207
+ write 'Rakefile', <<-TXT
208
+ # Rakefile
209
+ require 'rake'
210
+ require 'rake/clean'
211
+ require 'rake/testtask'
212
+ require 'tempfile'
213
+ require 'open3'
214
+
215
+ task :default => :test
216
+ task :test => 'test:all'
217
+
218
+ namespace 'test' do
219
+ Rake::TestTask.new('all') do |t|
220
+ t.libs << 'test'
221
+ t.test_files = FileList['test/nuts_*.rb']
222
+ end
223
+ end
224
+ TXT
225
+ end
226
+
227
+ # write a config.kdl
228
+ def make_configkdl
229
+ write 'config.kdl', <<-KDL
230
+ # config.kdl
231
+ hostname {
232
+ default "localhost"
233
+ }
234
+ KDL
235
+ end
236
+
237
+ # write a Gemfile
238
+ def make_gemfile
239
+ write 'Gemfile', <<-GEM
240
+ # frozen_string_literal: true
241
+ source 'https://rubygems.org'
242
+
243
+ gem 'camping'
244
+ gem 'puma'
245
+ gem 'rake'
246
+
247
+ group :production do
248
+ gem 'rack-ssl-enforcer'
249
+ end
250
+
251
+ group :development do
252
+ end
253
+
254
+ group :test do
255
+ gem 'minitest', '~> 5.0'
256
+ gem 'minitest-reporters'
257
+ gem 'rack-test'
258
+ end
259
+
260
+ GEM
261
+ end
262
+
263
+ # write a README.md
264
+ def make_readme
265
+ write 'README.md', <<-READ
266
+ # Camping
267
+ Camping is really fun and I hope you enjoy it.
268
+
269
+ Start camping by running: `camping` in the root directory.
270
+
271
+ READ
272
+ end
273
+
274
+ def make_public_folder
275
+ Dir.mkdir("public") unless Dir.exist?("public")
276
+ end
277
+
278
+ def make_test_folder
279
+ Dir.mkdir("test") unless Dir.exist?("test")
280
+ write 'test/test_helper.rb', <<-RUBY
281
+ $:.unshift File.dirname(__FILE__) + '/../'
282
+ # shift to act like we're in the regular degular directory
283
+
284
+ begin
285
+ require 'rubygems'
286
+ rescue LoadError
287
+ end
288
+
289
+ require 'camping'
290
+ require 'minitest/autorun'
291
+ require 'minitest'
292
+ require 'rack/test'
293
+ require "minitest/reporters"
294
+ Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(:color => true)]
295
+
296
+ class TestCase < MiniTest::Test
297
+ include Rack::Test::Methods
298
+
299
+ def self.inherited(mod)
300
+ mod.app = Object.const_get(mod.to_s[/\w+/])
301
+ super
302
+ end
303
+
304
+ class << self
305
+ attr_accessor :app
306
+ end
307
+
308
+ def body() last_response.body end
309
+ def app() self.class.app end
310
+
311
+ def assert_reverse
312
+ begin
313
+ yield
314
+ rescue Exception
315
+ else
316
+ assert false, "Block didn't fail"
317
+ end
318
+ end
319
+
320
+ def assert_body(str)
321
+ case str
322
+ when Regexp
323
+ assert_match(str, last_response.body.strip)
324
+ else
325
+ assert_equal(str.to_s, last_response.body.strip)
326
+ end
327
+ end
328
+
329
+ def assert_status(code)
330
+ assert_equal(code, last_response.status)
331
+ end
332
+
333
+ def test_silly; end
334
+
335
+ end
336
+
337
+ RUBY
338
+ end
339
+
340
+ end
341
+ end
342
+
343
+ class Commands
344
+
345
+ # A helper method to spit out Routes for an application
346
+ def self.routes(theApp = Camping, silent = false)
347
+ routes = Camping::CommandsHelpers::RoutesParser.parse theApp
348
+ routes.display unless silent == true
349
+ return routes
350
+ end
351
+
352
+ def self.new_cmd(app_name=:Camp)
353
+ app_name = :Camp if app_name == nil
354
+ app_name = app_name.to_sym if app_name.class == String
355
+
356
+ snake_app_name = Camping::CommandsHelpers.to_snake_case(app_name)
357
+
358
+ # make a directory then move there.
359
+ # _original_dir = Dir.pwd
360
+ Dir.mkdir("#{snake_app_name}") unless Dir.exist?("#{snake_app_name}")
361
+ Dir.chdir("#{snake_app_name}")
362
+
363
+ # generate a new camping app in a directory named after it:
364
+ Generators::make_camp_file(app_name)
365
+ Generators::make_gitignore()
366
+ Generators::make_rakefile()
367
+ Generators::make_ruby_version()
368
+ Generators::make_configkdl()
369
+ Generators::make_gemfile()
370
+ Generators::make_readme()
371
+ Generators::make_public_folder()
372
+ Generators::make_test_folder()
373
+
374
+ # optionally add omnibus support
375
+ # add src/ folder
376
+ # add lib/ folder
377
+ # add views/ folder
378
+
379
+ # optionally add a local database too, through guidebook
380
+ # add db/ folder
381
+ # add db/migrate folder
382
+ # add db/config.kdl
383
+ # append migrations stuff to Rakefile.
384
+ `ls`
385
+ end
386
+
387
+ end
388
+ end
@@ -0,0 +1,48 @@
1
+ # This gear is originally from filtering_camping gem by judofyr, and techarc.
2
+ module Gear
3
+ module Filters
4
+ module ClassMethods
5
+ def filters
6
+ @_filters ||= {:before => [], :after => []}
7
+ end
8
+
9
+ def before(actions, &blk)
10
+ actions = [actions] unless actions.is_a?(Array)
11
+ actions.each do |action|
12
+ filters[:before] << [action, blk]
13
+ end
14
+ end
15
+
16
+ def after(actions, &blk)
17
+ actions = [actions] unless actions.is_a?(Array)
18
+ actions.each do |action|
19
+ filters[:after] << [action, blk]
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.included(mod)
25
+ mod.extend(ClassMethods)
26
+ end
27
+
28
+ def self.setup(app, *a, &block) end
29
+
30
+ def run_filters(type)
31
+ o = self.class.to_s.split("::")
32
+ filters = Object.const_get(o.first).filters
33
+ filters[type].each do |filter|
34
+ if (filter[0].is_a?(Symbol) && (filter[0] == o.last.to_sym || filter[0] == :all)) ||
35
+ (filter[0].is_a?(String) && /^#{filter[0]}\/?$/ =~ @env.REQUEST_URI)
36
+ self.instance_eval(&filter[1])
37
+ end
38
+ end
39
+ end
40
+
41
+ def service(*a)
42
+ run_filters(:before)
43
+ super(*a)
44
+ run_filters(:after)
45
+ self
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+
2
+ # This gear adds inspection utilities to Camping
3
+ module Gear
4
+ module Inspection
5
+
6
+ # reserved module for Camping Class Methods we add because we're the best.
7
+ module ClassMethods
8
+
9
+ end
10
+
11
+ module ControllersClassMethods
12
+ # All Helper helps us inspect our Controllers from outside of the app.
13
+ # TODO: Move to CampTools for introspection.
14
+ def all
15
+ all = []
16
+ constants.map { |c|
17
+ all << c.name if !["I", "Camper"].include? c.to_s
18
+ }
19
+ all
20
+ end
21
+ end
22
+
23
+ def self.included(mod)
24
+ mod.extend(ClassMethods)
25
+ mod::Controllers.extend(ControllersClassMethods)
26
+ end
27
+
28
+ # empty setup as determined by the Camping Gear Spec API.
29
+ def self.setup(app, *a, &block) end
30
+
31
+ end
32
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+ # lib/camping/config
3
+ # load and parse settings.
4
+
5
+ begin
6
+ require 'kdl'
7
+ rescue LoadError => e
8
+ raise "kdl could not be loaded (is it installed?): #{e.message}"
9
+ end
10
+
11
+ module Gear
12
+
13
+ # Namespace to hide all of the KDL Configure stuff.
14
+ module Kuddly
15
+ WARNINGS = []
16
+
17
+ module ClassMethods
18
+ def configure(app)
19
+ config = Gear::Kuddly.get_config()
20
+ config.each do |k,v|
21
+ app.set(k.to_sym, v)
22
+ end unless config == nil
23
+ end
24
+ end
25
+
26
+ class << self
27
+
28
+ def included(mod)
29
+ mod.extend(ClassMethods)
30
+ end
31
+
32
+ # required for compliance reasons
33
+ def setup(app, *a, &block) end
34
+
35
+ def kdl_error_message(kdl_string="",error_message="", error=nil)
36
+ # parse error message to get line number and column:
37
+ m = error_message.match( /\((\d)+:(\d)\)/ )
38
+
39
+ if m == nil
40
+ warn "kdl_error_message was called on a nil error message?"
41
+ warn "message: #{error_message}"
42
+ warn "kdl_string: #{kdl_string}"
43
+ warn "current dir: #{Dir.pwd}"
44
+ warn "#{error}"
45
+ return
46
+ end
47
+
48
+ line = m[1].to_i
49
+ lines = kdl_string.split( "\n" )
50
+
51
+ em = "\n"
52
+ em << "#{line-4}: #{lines[line-4]}\n" if (line-4) > 0 && (line-4) < lines.count
53
+ em << "#{line-3}: #{lines[line-3]}\n" if (line-3) > 0 && (line-3) < lines.count
54
+ em << "#{line-2}: #{lines[line-2]}\n" if (line-2) > 0 && (line-2) < lines.count
55
+ em << "#{line-1}: #{lines[line-1]}\n" if (line-1) > 0 && (line-1) < lines.count
56
+ em << "#{line}: #{lines[line]}\n" if (line)
57
+ em << "#{line+1}: #{lines[line+1]}\n" if (line+1) > 0 && (line+1) < lines.count
58
+ em << "#{line+2}: #{lines[line+2]}\n" if (line+2) > 0 && (line+2) < lines.count
59
+ em << "#{line+3}: #{lines[line+3]}\n" if (line+3) > 0 && (line+3) < lines.count
60
+ em << "#{line+4}: #{lines[line+4]}\n" if (line+4) > 0 && (line+4) < lines.count
61
+ # em << "\n"
62
+ WARNINGS << em
63
+ end
64
+
65
+
66
+ # parses a kdl file into a kdl document Object.
67
+ # returns nil if it's false. Also assumes that the file is exists.
68
+ # an optional silence_warnings parameter is set to false. This is used for
69
+ # testing.
70
+ def parse_kdl(config_file = nil, silence_warnings = false)
71
+ begin
72
+ kdl_string = File.open(config_file).read
73
+ rescue => error # Errno::ENOENT
74
+ puts ""
75
+ puts "Error trying to read a config file: \"#{error}.\""
76
+ puts " Attempted to open: #{config_file}"
77
+ puts " Current directory: #{Dir.pwd}"
78
+ puts " files in directory: #{Dir.glob('*')}"
79
+ puts ""
80
+ end
81
+
82
+ begin
83
+ kdl_doc = KDL.parse_document(kdl_string)
84
+ rescue => error
85
+ warn "#{error}"
86
+ # parse error message to get line number and column:
87
+ message = Kuddly.kdl_error_message(kdl_string, error.message, error)
88
+ m = error.message.match( /\((\d)+:(\d)\)/ )
89
+
90
+ line, column = m[1].to_i, m[2].to_i if m.respond_to? '[]'
91
+
92
+ warn("\nError parsing config: #{config_file}, on line: #{line}, at column: #{column}.", message, "#{error.message}", uplevel: 1) unless silence_warnings
93
+ end
94
+
95
+ kdl_doc
96
+ end
97
+
98
+ # Maps kdl settings. Settings Example:
99
+ # ```
100
+ # database {
101
+ # default adapter="sqlite3" database="#{database}" host="localhost" max_connections=5 timeout=5000
102
+ # development
103
+ # test
104
+ # production adapter="postgres" database="kow"
105
+ # }
106
+ # ```
107
+ def map_kdl(kdl_doc=nil)
108
+ configs = {}
109
+
110
+ if kdl_doc
111
+
112
+ # We have a kdl document, so that's good.
113
+ # iterate through each top level node to see what kind of data we have.
114
+ kdl_doc.nodes.each do |d|
115
+ config_name = d.name.to_sym
116
+ configs[config_name] = {}
117
+
118
+ if d.children.length > 0
119
+ # we've got kids!
120
+ # This node will have sub nodes with properties
121
+ d.children.each do |en|
122
+ env_name = en.name.to_sym
123
+ # parse the settings for each environment
124
+ configs[config_name][env_name] = {}
125
+ en.properties.each do |key, value|
126
+ configs[config_name][env_name][key.to_sym] = value.value
127
+ end
128
+ end
129
+ else
130
+ # we've got raw data, so place it into a default hash spot.
131
+ vals = []
132
+ if d.arguments.length > 1
133
+ d.arguments.each { |v| vals << v.value }
134
+ else
135
+ vals = d.arguments.first.value
136
+ end
137
+
138
+ configs[config_name]['default'] = vals
139
+ end
140
+ end
141
+ end
142
+
143
+ configs
144
+ end
145
+
146
+ # get_config
147
+ # searches for any kdl document inside of the root folder
148
+ # Then parses it, and merges the data based on the current environment.
149
+ def get_config(provided_config_file = nil)
150
+
151
+ config_file, kdl_doc, merged_configs = provided_config_file, nil, {}
152
+ config_file = get_root_config_file() unless provided_config_file != nil
153
+
154
+ # If the config file is just nil then we probably don't have one.
155
+ return nil unless config_file != nil
156
+
157
+ # parses then maps the kdl
158
+ configs = map_kdl(parse_kdl(config_file))
159
+ env = ENV['environment'] ||= "development"
160
+
161
+ configs.each do |key, setting|
162
+ if setting.has_key? :default && env.to_sym
163
+ merged_configs[key] = setting[:default].merge(setting[env.to_sym])
164
+ else
165
+ merged_configs[key] = setting['default']
166
+ end
167
+ end
168
+
169
+ merged_configs
170
+ end
171
+
172
+ # get kdl config file
173
+ def get_root_config_file(search_pattern = "config.kdl")
174
+ Dir.glob(search_pattern).first
175
+ end
176
+ end
177
+ end
178
+ end