camping 2.1.532 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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