ella 0.1.2

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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +3 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +12 -0
  7. data/Gemfile.lock +56 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +115 -0
  10. data/ella-0.1.0.gem +0 -0
  11. data/ella.gemspec +26 -0
  12. data/exe/ella +14 -0
  13. data/lib/ella.rb +45 -0
  14. data/lib/ella/cli.rb +114 -0
  15. data/lib/ella/controller.rb +36 -0
  16. data/lib/ella/generator.rb +42 -0
  17. data/lib/ella/generator/config_generator.rb +16 -0
  18. data/lib/ella/generator/controller_generator.rb +34 -0
  19. data/lib/ella/generator/destroyer.rb +44 -0
  20. data/lib/ella/generator/gemfile_generator.rb +15 -0
  21. data/lib/ella/generator/model_generator.rb +27 -0
  22. data/lib/ella/generator/project_generator.rb +88 -0
  23. data/lib/ella/generator/rackfile_generator.rb +76 -0
  24. data/lib/ella/generator/view_generator.rb +42 -0
  25. data/lib/ella/log.rb +70 -0
  26. data/lib/ella/model.rb +0 -0
  27. data/lib/ella/name_formatter.rb +32 -0
  28. data/lib/ella/pipeline.rb +99 -0
  29. data/lib/ella/reloader.rb +430 -0
  30. data/lib/ella/server.rb +107 -0
  31. data/lib/ella/template.rb +46 -0
  32. data/lib/ella/test.rb +9 -0
  33. data/lib/ella/version.rb +3 -0
  34. data/lib/ella/view.rb +0 -0
  35. data/templates/Gemfile +12 -0
  36. data/templates/configs/css.rb +20 -0
  37. data/templates/configs/js.rb +19 -0
  38. data/templates/configs/puma.rb +5 -0
  39. data/templates/controller +9 -0
  40. data/templates/controllers/root.rb +7 -0
  41. data/templates/main.rb +8 -0
  42. data/templates/model +6 -0
  43. data/templates/test +22 -0
  44. data/templates/views/layout.erb +18 -0
  45. data/templates/views/root/index.erb +3 -0
  46. data/version.txt +1 -0
  47. metadata +94 -0
File without changes
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # Ruby and rubyist convention demand that the project name be formatted in
5
+ # different ways depending on context. This class helps prevent repetition
6
+ # of name formatting.
7
+ # Thousands of Edabit exercises have prepared me for this, my finest hour.
8
+ # I am currently assuming that no one will be initializing in or using
9
+ # camelCase, as that format seems to have very little use in the Ruby
10
+ # community.
11
+ class NameFormatter
12
+ attr_reader :snake_case
13
+
14
+ def initialize(name)
15
+ Ella.abort('Project name must be a valid string.') if name.nil? || name.empty?
16
+ # If the project name is given in Pascal Case, save as snake case.
17
+ @snake_case = name =~ /^[A-Z]/ ? name.gsub(/([A-Z])/, '_\1')[1..-1].downcase : name
18
+ end
19
+
20
+ def file_name
21
+ "#{@snake_case}.rb"
22
+ end
23
+
24
+ def pascal_case
25
+ @snake_case.split('_').map(&:capitalize).join('')
26
+ end
27
+
28
+ def human
29
+ @snake_case.split('_').map(&:capitalize).join(' ')
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+
5
+ # Extra-special Message
6
+ # ---------------------
7
+ #
8
+ # I started making Ella because I found writing my own quick asset pipelines
9
+ # was much easier and faster and more bug-free than the "modern" and "correct"
10
+ # solutions. YAGNI should be a major cornerstorne of Ella.
11
+ #
12
+ # Rake may be considered iff this file ever becomes overly-complicated or slow,
13
+ # but I want to make sure that an easy pipeline is available by default. The
14
+ # user can set up another pipeline if they need something more complicated.
15
+ #
16
+ # -- kmc
17
+
18
+ module Ella
19
+ # Custom made super-simple assets pipeline. This KISS philosophy of this
20
+ # pipeline is:
21
+ #
22
+ # "data from files" --> "user defined filter in Ruby" --> "output file"
23
+ #
24
+ # Of course, Ella is modular, so the user should be able to disable it and
25
+ # set up their own assets pipeline of choice.
26
+ class Pipeline
27
+ def initialize(pipeline_type)
28
+ @type = pipeline_type
29
+
30
+ Log.info("Initializing #{@type.upcase} pipeline...")
31
+ set_io_directories
32
+ initialize_tempfile
33
+ load File.join(Dir.pwd, "configs/#{@type}.rb")
34
+ end
35
+
36
+ # Because this is user-defined code, any exception is possible. Having
37
+ # to restart the development server every time there is some error in
38
+ # the filter is *NOT DESIRABLE*.
39
+ def listen
40
+ run # Public files are not persistent, so this must be run on startup.
41
+ @listener = Listen.to(@input_dir) do |modified, added, removed|
42
+ report_listen_results(modified, added, removed)
43
+ run
44
+ rescue => e
45
+ report_listen_error(e)
46
+ end
47
+ @listener.start
48
+ end
49
+
50
+ def run
51
+ @tempfile.close
52
+ @tempfile.unlink
53
+ @tempfile = Tempfile.new(['', ".#{@type}"], @output_dir)
54
+ @tempfile.write(filter)
55
+ @tempfile.rewind
56
+ end
57
+
58
+ private
59
+
60
+ def set_io_directories
61
+ @input_dir = File.join(Dir.pwd, "assets/#{@type}")
62
+ @output_dir = File.join(Dir.pwd, "public/#{@type}")
63
+ Dir.mkdir(@input_dir) unless Dir.exist?(@input_dir)
64
+ Dir.mkdir(@output_dir) unless Dir.exist?(@output_dir)
65
+ end
66
+
67
+ def initialize_tempfile
68
+ @tempfile = Tempfile.new(['', ".#{@type}"], @output_dir)
69
+ end
70
+
71
+ def report_listen_results(modified, added, removed)
72
+ Log.info("#{@type.capitalize} Pipeline detected change:")
73
+ Log.info("Modified: #{modified}") if modified
74
+ Log.info("Modified: #{added}") if added
75
+ Log.info("Modified: #{removed}") if removed
76
+ Log.info("#{@type.capitalize} Pipeline recompiling")
77
+ end
78
+
79
+ def report_listen_error(e)
80
+ Log.error('Error in listener:')
81
+ puts e.backtrace
82
+ puts e.inspect
83
+ end
84
+
85
+ def filter
86
+ # TODO: Something more dynamic should go here. People should be able to
87
+ # create their own pipelines.
88
+ if @type == 'css'
89
+ css
90
+ elsif @type == 'js'
91
+ js
92
+ end
93
+ end
94
+
95
+ def asset_data(*files)
96
+ files.inject('') { |str, fname| str += File.read(File.join(@input_dir, fname)) }
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,430 @@
1
+ require 'sinatra/base'
2
+
3
+ # Copyright (c) 2007, 2008, 2009 Blake Mizerany
4
+ # Copyright (c) 2010-2017 Konstantin Haase
5
+ # Copyright (c) 2015-2017 Zachary Scott
6
+
7
+ # This is a so-far-untouched fork of the listener in 'sinatra-contrib', and is
8
+ # destributed under the MIT license.
9
+ # For some reason some combination of updates messed up the paths and made
10
+ # the version from the "sinatra-contrib" fail to make it to the Ruby load path.
11
+ # It did not seem like a good idea for one gem to mess with the load path to
12
+ # accommodate another gem.
13
+ # Either this will be maintained as part of Ella, or Ella will switch back to
14
+ # sinatra-contrib whenever the mysterious issue is resolved. The future's not
15
+ # ours to see.
16
+ module Sinatra
17
+
18
+ # = Sinatra::Reloader
19
+ #
20
+ # Extension to reload modified files. Useful during development,
21
+ # since it will automatically require files defining routes, filters,
22
+ # error handlers and inline templates, with every incoming request,
23
+ # but only if they have been updated.
24
+ #
25
+ # == Usage
26
+ #
27
+ # === Classic Application
28
+ #
29
+ # To enable the reloader in a classic application all you need to do is
30
+ # require it:
31
+ #
32
+ # require "sinatra"
33
+ # require "sinatra/reloader" if development?
34
+ #
35
+ # # Your classic application code goes here...
36
+ #
37
+ # === Modular Application
38
+ #
39
+ # To enable the reloader in a modular application all you need to do is
40
+ # require it, and then, register it:
41
+ #
42
+ # require "sinatra/base"
43
+ # require "sinatra/reloader"
44
+ #
45
+ # class MyApp < Sinatra::Base
46
+ # configure :development do
47
+ # register Sinatra::Reloader
48
+ # end
49
+ #
50
+ # # Your modular application code goes here...
51
+ # end
52
+ #
53
+ # == Using the Reloader in Other Environments
54
+ #
55
+ # By default, the reloader is only enabled for the development
56
+ # environment. Similar to registering the reloader in a modular
57
+ # application, a classic application requires manually enabling the
58
+ # extension for it to be available in a non-development environment.
59
+ #
60
+ # require "sinatra"
61
+ # require "sinatra/reloader"
62
+ #
63
+ # configure :production do
64
+ # enable :reloader
65
+ # end
66
+ #
67
+ # == Changing the Reloading Policy
68
+ #
69
+ # You can refine the reloading policy with +also_reload+ and
70
+ # +dont_reload+, to customize which files should, and should not, be
71
+ # reloaded, respectively. You can also use +after_reload+ to execute a
72
+ # block after any file being reloaded.
73
+ #
74
+ # === Classic Application
75
+ #
76
+ # Simply call the methods:
77
+ #
78
+ # require "sinatra"
79
+ # require "sinatra/reloader" if development?
80
+ #
81
+ # also_reload '/path/to/some/file'
82
+ # dont_reload '/path/to/other/file'
83
+ # after_reload do
84
+ # puts 'reloaded'
85
+ # end
86
+ #
87
+ # # Your classic application code goes here...
88
+ #
89
+ # === Modular Application
90
+ #
91
+ # Call the methods inside the +configure+ block:
92
+ #
93
+ # require "sinatra/base"
94
+ # require "sinatra/reloader"
95
+ #
96
+ # class MyApp < Sinatra::Base
97
+ # configure :development do
98
+ # register Sinatra::Reloader
99
+ # also_reload '/path/to/some/file'
100
+ # dont_reload '/path/to/other/file'
101
+ # after_reload do
102
+ # puts 'reloaded'
103
+ # end
104
+ # end
105
+ #
106
+ # # Your modular application code goes here...
107
+ # end
108
+ #
109
+ module Reloader
110
+
111
+ # Watches a file so it can tell when it has been updated, and what
112
+ # elements does it contain.
113
+ class Watcher
114
+
115
+ # Represents an element of a Sinatra application that may need to
116
+ # be reloaded. An element could be:
117
+ # * a route
118
+ # * a filter
119
+ # * an error handler
120
+ # * a middleware
121
+ # * inline templates
122
+ #
123
+ # Its +representation+ attribute is there to allow to identify the
124
+ # element within an application, that is, to match it with its
125
+ # Sinatra's internal representation.
126
+ class Element < Struct.new(:type, :representation)
127
+ end
128
+
129
+ # Collection of file +Watcher+ that can be associated with a
130
+ # Sinatra application. That way, we can know which files belong
131
+ # to a given application and which files have been modified. It
132
+ # also provides a mechanism to inform a Watcher of the elements
133
+ # defined in the file being watched and if its changes should be
134
+ # ignored.
135
+ class List
136
+ @app_list_map = Hash.new { |hash, key| hash[key] = new }
137
+
138
+ # Returns the +List+ for the application +app+.
139
+ def self.for(app)
140
+ @app_list_map[app]
141
+ end
142
+
143
+ # Creates a new +List+ instance.
144
+ def initialize
145
+ @path_watcher_map = Hash.new do |hash, key|
146
+ hash[key] = Watcher.new(key)
147
+ end
148
+ end
149
+
150
+ # Lets the +Watcher+ for the file located at +path+ know that the
151
+ # +element+ is defined there, and adds the +Watcher+ to the +List+,
152
+ # if it isn't already there.
153
+ def watch(path, element)
154
+ watcher_for(path).elements << element
155
+ end
156
+
157
+ # Tells the +Watcher+ for the file located at +path+ to ignore
158
+ # the file changes, and adds the +Watcher+ to the +List+, if
159
+ # it isn't already there.
160
+ def ignore(path)
161
+ watcher_for(path).ignore
162
+ end
163
+
164
+ # Adds a +Watcher+ for the file located at +path+ to the
165
+ # +List+, if it isn't already there.
166
+ def watcher_for(path)
167
+ @path_watcher_map[File.expand_path(path)]
168
+ end
169
+ alias watch_file watcher_for
170
+
171
+ # Returns an array with all the watchers in the +List+.
172
+ def watchers
173
+ @path_watcher_map.values
174
+ end
175
+
176
+ # Returns an array with all the watchers in the +List+ that
177
+ # have been updated.
178
+ def updated
179
+ watchers.find_all(&:updated?)
180
+ end
181
+ end
182
+
183
+ attr_reader :path, :elements, :mtime
184
+
185
+ # Creates a new +Watcher+ instance for the file located at +path+.
186
+ def initialize(path)
187
+ @ignore = nil
188
+ @path, @elements = path, []
189
+ update
190
+ end
191
+
192
+ # Indicates whether or not the file being watched has been modified.
193
+ def updated?
194
+ !ignore? && !removed? && mtime != File.mtime(path)
195
+ end
196
+
197
+ # Updates the mtime of the file being watched.
198
+ def update
199
+ @mtime = File.mtime(path)
200
+ end
201
+
202
+ # Indicates whether or not the file being watched has inline
203
+ # templates.
204
+ def inline_templates?
205
+ elements.any? { |element| element.type == :inline_templates }
206
+ end
207
+
208
+ # Informs that the modifications to the file being watched
209
+ # should be ignored.
210
+ def ignore
211
+ @ignore = true
212
+ end
213
+
214
+ # Indicates whether or not the modifications to the file being
215
+ # watched should be ignored.
216
+ def ignore?
217
+ !!@ignore
218
+ end
219
+
220
+ # Indicates whether or not the file being watched has been removed.
221
+ def removed?
222
+ !File.exist?(path)
223
+ end
224
+ end
225
+
226
+ MUTEX_FOR_PERFORM = Mutex.new
227
+
228
+ # Allow a block to be executed after any file being reloaded
229
+ @@after_reload = []
230
+ def after_reload(&block)
231
+ @@after_reload << block
232
+ end
233
+
234
+ # When the extension is registered it extends the Sinatra application
235
+ # +klass+ with the modules +BaseMethods+ and +ExtensionMethods+ and
236
+ # defines a before filter to +perform+ the reload of the modified files.
237
+ def self.registered(klass)
238
+ @reloader_loaded_in ||= {}
239
+ return if @reloader_loaded_in[klass]
240
+
241
+ @reloader_loaded_in[klass] = true
242
+
243
+ klass.extend BaseMethods
244
+ klass.extend ExtensionMethods
245
+ klass.set(:reloader) { klass.development? }
246
+ klass.set(:reload_templates) { klass.reloader? }
247
+ klass.before do
248
+ if klass.reloader?
249
+ MUTEX_FOR_PERFORM.synchronize { Reloader.perform(klass) }
250
+ end
251
+ end
252
+ klass.set(:inline_templates, klass.app_file) if klass == Sinatra::Application
253
+ end
254
+
255
+ # Reloads the modified files, adding, updating and removing the
256
+ # needed elements.
257
+ def self.perform(klass)
258
+ Watcher::List.for(klass).updated.each do |watcher|
259
+ klass.set(:inline_templates, watcher.path) if watcher.inline_templates?
260
+ watcher.elements.each { |element| klass.deactivate(element) }
261
+ $LOADED_FEATURES.delete(watcher.path)
262
+ require watcher.path
263
+ watcher.update
264
+ end
265
+ @@after_reload.each(&:call)
266
+ end
267
+
268
+ # Contains the methods defined in Sinatra::Base that are overridden.
269
+ module BaseMethods
270
+ # Protects Sinatra::Base.run! from being called more than once.
271
+ def run!(*args)
272
+ if settings.reloader?
273
+ super unless running?
274
+ else
275
+ super
276
+ end
277
+ end
278
+
279
+ # Does everything Sinatra::Base#route does, but it also tells the
280
+ # +Watcher::List+ for the Sinatra application to watch the defined
281
+ # route.
282
+ #
283
+ # Note: We are using #compile! so we don't interfere with extensions
284
+ # changing #route.
285
+ def compile!(verb, path, block, **options)
286
+ source_location = block.respond_to?(:source_location) ?
287
+ block.source_location.first : caller_files[1]
288
+ signature = super
289
+ watch_element(
290
+ source_location, :route, { :verb => verb, :signature => signature }
291
+ )
292
+ signature
293
+ end
294
+
295
+ # Does everything Sinatra::Base#inline_templates= does, but it also
296
+ # tells the +Watcher::List+ for the Sinatra application to watch the
297
+ # inline templates in +file+ or the file who made the call to this
298
+ # method.
299
+ def inline_templates=(file=nil)
300
+ file = (file.nil? || file == true) ?
301
+ (caller_files[1] || File.expand_path($0)) : file
302
+ watch_element(file, :inline_templates)
303
+ super
304
+ end
305
+
306
+ # Does everything Sinatra::Base#use does, but it also tells the
307
+ # +Watcher::List+ for the Sinatra application to watch the middleware
308
+ # being used.
309
+ def use(middleware, *args, &block)
310
+ path = caller_files[1] || File.expand_path($0)
311
+ watch_element(path, :middleware, [middleware, args, block])
312
+ super
313
+ end
314
+
315
+ # Does everything Sinatra::Base#add_filter does, but it also tells
316
+ # the +Watcher::List+ for the Sinatra application to watch the defined
317
+ # filter.
318
+ def add_filter(type, path = nil, **options, &block)
319
+ source_location = block.respond_to?(:source_location) ?
320
+ block.source_location.first : caller_files[1]
321
+ result = super
322
+ watch_element(source_location, :"#{type}_filter", filters[type].last)
323
+ result
324
+ end
325
+
326
+ # Does everything Sinatra::Base#error does, but it also tells the
327
+ # +Watcher::List+ for the Sinatra application to watch the defined
328
+ # error handler.
329
+ def error(*codes, &block)
330
+ path = caller_files[1] || File.expand_path($0)
331
+ result = super
332
+ codes.each do |c|
333
+ watch_element(path, :error, :code => c, :handler => @errors[c])
334
+ end
335
+ result
336
+ end
337
+
338
+ # Does everything Sinatra::Base#register does, but it also lets the
339
+ # reloader know that an extension is being registered, because the
340
+ # elements defined in its +registered+ method need a special treatment.
341
+ def register(*extensions, &block)
342
+ start_registering_extension
343
+ result = super
344
+ stop_registering_extension
345
+ result
346
+ end
347
+
348
+ # Does everything Sinatra::Base#register does and then registers the
349
+ # reloader in the +subclass+.
350
+ def inherited(subclass)
351
+ result = super
352
+ subclass.register Sinatra::Reloader
353
+ result
354
+ end
355
+ end
356
+
357
+ # Contains the methods that the extension adds to the Sinatra application.
358
+ module ExtensionMethods
359
+ # Removes the +element+ from the Sinatra application.
360
+ def deactivate(element)
361
+ case element.type
362
+ when :route then
363
+ verb = element.representation[:verb]
364
+ signature = element.representation[:signature]
365
+ (routes[verb] ||= []).delete(signature)
366
+ when :middleware then
367
+ @middleware.delete(element.representation)
368
+ when :before_filter then
369
+ filters[:before].delete(element.representation)
370
+ when :after_filter then
371
+ filters[:after].delete(element.representation)
372
+ when :error then
373
+ code = element.representation[:code]
374
+ handler = element.representation[:handler]
375
+ @errors.delete(code) if @errors[code] == handler
376
+ end
377
+ end
378
+
379
+ # Indicates with a +glob+ which files should be reloaded if they
380
+ # have been modified. It can be called several times.
381
+ def also_reload(*glob)
382
+ Dir[*glob].each { |path| Watcher::List.for(self).watch_file(path) }
383
+ end
384
+
385
+ # Indicates with a +glob+ which files should not be reloaded even if
386
+ # they have been modified. It can be called several times.
387
+ def dont_reload(*glob)
388
+ Dir[*glob].each { |path| Watcher::List.for(self).ignore(path) }
389
+ end
390
+
391
+ private
392
+
393
+ # attr_reader :register_path warn on -w (private attribute)
394
+ def register_path; @register_path ||= nil; end
395
+
396
+ # Indicates an extesion is being registered.
397
+ def start_registering_extension
398
+ @register_path = caller_files[2]
399
+ end
400
+
401
+ # Indicates the extesion has already been registered.
402
+ def stop_registering_extension
403
+ @register_path = nil
404
+ end
405
+
406
+ # Indicates whether or not an extension is being registered.
407
+ def registering_extension?
408
+ !register_path.nil?
409
+ end
410
+
411
+ # Builds a Watcher::Element from +type+ and +representation+ and
412
+ # tells the Watcher::List for the current application to watch it
413
+ # in the file located at +path+.
414
+ #
415
+ # If an extension is being registered, it also tells the list to
416
+ # watch it in the file where the extension has been registered.
417
+ # This prevents the duplication of the elements added by the
418
+ # extension in its +registered+ method with every reload.
419
+ def watch_element(path, type, representation=nil)
420
+ list = Watcher::List.for(self)
421
+ element = Watcher::Element.new(type, representation)
422
+ list.watch(path, element)
423
+ list.watch(register_path, element) if registering_extension?
424
+ end
425
+ end
426
+ end
427
+
428
+ register Reloader
429
+ Delegator.delegate :also_reload, :dont_reload
430
+ end