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
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/base'
4
+ require_relative 'reloader'
5
+
6
+ module Ella
7
+ # Sinatra was not specifically designed to have different modular controller
8
+ # files. Some of the magic needs to go here, and maintaining a specific
9
+ # loading order is important: seemingly insignificant changes can break the
10
+ # little hacks that allow Sinatra to be used as a macro-framework.
11
+ class Controller < Sinatra::Base
12
+ set :views, Dir.pwd + '/views' # TODO This can be more dynamic.
13
+ set :public_folder, Dir.pwd + '/public'
14
+
15
+ configure :development do
16
+ register Sinatra::Reloader
17
+ also_reload File.join(Dir.pwd, 'models/*')
18
+ also_reload File.join(Dir.pwd, 'controllers/*')
19
+ after_reload do
20
+ puts('Reloading')
21
+ end
22
+ end
23
+
24
+ # TODO: Some fancy-pants metaprogramming to allow for all user-defined
25
+ # pipelines.
26
+ def js_path
27
+ filename = Dir.glob("public/js/*").max_by {|f| File.mtime(f)}
28
+ filename.sub('public', '')
29
+ end
30
+
31
+ def css_path
32
+ filename = Dir.glob("public/css/*").max_by {|f| File.mtime(f)}
33
+ filename.sub('public', '')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # This is an abstract class for the numerous generators that make up the bulk
5
+ # of this project. It cannot be run on its own.
6
+ # TODO: The first parameter should be part of the hash, for clarity.
7
+ class Generator
8
+ def initialize(directory: nil, files: [], templates: [], template_vars: {})
9
+ @directory = NameFormatter.new(directory) if directory
10
+ @files = files
11
+ @templates = templates
12
+ @template_vars = template_vars
13
+ end
14
+
15
+ def run
16
+ raise NotImplementedError, 'Subclasses must define \'run\'.'
17
+ end
18
+
19
+ private
20
+
21
+ def generator_type
22
+ self.class.name.gsub(/^.*::(.*)Generator$/, '\1').downcase + 's'
23
+ end
24
+
25
+ def make_directory(special_name = nil)
26
+ target = special_name || generator_type
27
+ return if Dir.exist?(target)
28
+
29
+ Log.create(target + '/')
30
+ Dir.mkdir(target)
31
+ end
32
+
33
+ def make_test_directory
34
+ make_directory('tests')
35
+ make_directory("tests/#{generator_type}")
36
+ end
37
+
38
+ def target_name
39
+ @directory ? @directory.snake_case : @directory
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # This class generates Ella project config files, which are needed to nicely
5
+ # configure Puma and Sinatra.
6
+ class ConfigGenerator < Generator
7
+ def run
8
+ Ella.find_root
9
+ make_directory('log') # necessary for Puma config
10
+ make_directory # make config directory
11
+ Ella::Template.new('configs/puma.rb').write
12
+ Ella::Template.new('configs/css.rb').write
13
+ Ella::Template.new('configs/js.rb').write
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # Generator for controllers.
5
+ class ControllerGenerator < Generator
6
+ def run
7
+ # In the case of a controller, 'directory' and 'files' corresponds to the
8
+ # views being controlled. Every controller file represents a directory,
9
+ # and every controller route represents a file.
10
+ Ella.find_root
11
+ make_directory
12
+ @template_vars[:name] = @directory
13
+ @template_vars[:files] = @files
14
+ generate_controllers
15
+ generate_tests
16
+ end
17
+
18
+ private
19
+
20
+ # The root controller is a special case and does not use the generic template.
21
+ def generate_controllers
22
+ path = "controllers/#{@directory.snake_case}.rb"
23
+ template = (@directory.snake_case == 'root' ? nil : 'controller')
24
+ Ella::Template.new(path, generic_template: template, template_vars: @template_vars).write
25
+ end
26
+
27
+ def generate_tests
28
+ make_test_directory
29
+ path = "tests/controllers/#{@directory.snake_case}_test.rb"
30
+ @template_vars[:controller] = true
31
+ Ella::Template.new(path, generic_template: 'test', template_vars: @template_vars).write
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # This reverses the results of the m/v/c and test generators. Right now it
5
+ # destroys everything of a certain name if found. While that seems fairly
6
+ # blunt, there does not currenlty seem to be a reason to make it more
7
+ # specific.
8
+ # Although this is the opposite of a generator, the initialization requires
9
+ # the exact same things, and it makes sense to have the same interface, so it
10
+ # inherits from Generator.
11
+ class Destroyer < Generator
12
+ def run
13
+ @something_destroyed = false
14
+
15
+ attempt_to_destroy('models')
16
+ attempt_to_destroy('views')
17
+ attempt_to_destroy('controllers')
18
+ # TODO: destroy tests, once rspec is set up
19
+
20
+ Log.info('Nothing to destroy.') unless @something_destroyed
21
+ end
22
+
23
+ private
24
+
25
+ def destroy_dir(path)
26
+ Log.destroy(path)
27
+ Dir.delete(path)
28
+ @something_destroyed = true
29
+ end
30
+
31
+ def destroy_file(path)
32
+ Log.destroy(path)
33
+ File.delete(path)
34
+ @something_destroyed = true
35
+ end
36
+
37
+ def attempt_to_destroy(type)
38
+ dir_path = File.join(Dir.pwd, "#{type}/#{@directory.snake_case}")
39
+ file_path = File.join(Dir.pwd, "#{type}/#{@directory.file_name}")
40
+ destroy_dir(dir_path) if Dir.exist?(dir_path)
41
+ destroy_file(file_path) if File.exist?(file_path)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # Generates a Gemfile with the necessary dependencies for an ella project, and runs Bundle to
5
+ # install those dependencies.
6
+ class GemfileGenerator < Generator
7
+ def run
8
+ Ella.find_root
9
+ Ella::Template.new('Gemfile').write
10
+ Log.newline
11
+ Log.info('Running Bundle')
12
+ # Log.say(`bundle update`)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # Generator for models.
5
+ class ModelGenerator < Generator
6
+ def run
7
+ Ella.find_root
8
+ make_directory
9
+ @template_vars[:name] = @directory
10
+ generate_models
11
+ generate_tests
12
+ end
13
+
14
+ private
15
+
16
+ def generate_models
17
+ path = "models/#{@directory.snake_case}.rb"
18
+ Ella::Template.new(path, generic_template: 'model', template_vars: @template_vars).write
19
+ end
20
+
21
+ def generate_tests
22
+ make_test_directory
23
+ path = "tests/models/#{@directory.snake_case}_test.rb"
24
+ Ella::Template.new(path, generic_template: 'test', template_vars: @template_vars).write
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # This beast of a subclass generates a new Ella project from scratch.
5
+ # --------
6
+ # Ella requires a main project file for almost everything. Consider this,
7
+ # anyone who might want to change the order of this method.
8
+ class ProjectGenerator < Generator
9
+ def run
10
+ Log.yell("Generating New Ella Project: \"#{@directory.human}\"")
11
+
12
+ prepare_project_directory
13
+ create_main_project_file
14
+ initialize_git
15
+ generate_pipeline_directories
16
+ initialize_config
17
+ generate_model_directory
18
+ generate_views
19
+ generate_controllers
20
+ initialize_bundle
21
+ end
22
+
23
+ private
24
+
25
+ def prepare_project_directory
26
+ if Dir.entries('.').include?(@directory.snake_case)
27
+ Log.error('A directory with that name already exists. Aborting.')
28
+ abort
29
+ end
30
+ Log.newline
31
+ Log.info('Creating project core')
32
+ make_directory(@directory.snake_case)
33
+ Dir.chdir(@directory.snake_case)
34
+ make_directory('temp')
35
+ end
36
+
37
+ def create_main_project_file
38
+ Ella::Template.new('main.rb', template_vars: { name: @directory }).write
39
+ Log.newline
40
+ end
41
+
42
+ def initialize_git
43
+ Log.info('Initializing git')
44
+ `git init`
45
+ end
46
+
47
+ def generate_pipeline_directories
48
+ Log.info('Generating pipeline directories')
49
+ make_directory('assets')
50
+ make_directory('assets/css')
51
+ make_directory('assets/js')
52
+ make_directory('public')
53
+ make_directory('public/css')
54
+ make_directory('public/js')
55
+ Log.newline
56
+ end
57
+
58
+ def initialize_config
59
+ Log.info('Generating configuration files')
60
+ ConfigGenerator.new.run
61
+ Log.newline
62
+ end
63
+
64
+ def generate_model_directory
65
+ make_directory('models')
66
+ Log.newline
67
+ end
68
+
69
+ def generate_views
70
+ Log.info('Generating views')
71
+ ViewGenerator.new(templates: ['layout'], template_vars: { name: @directory }).run
72
+ ViewGenerator.new(directory: 'root', templates: ['index']).run
73
+ Log.newline
74
+ end
75
+
76
+ def generate_controllers
77
+ Log.info('Generating root controller')
78
+ ControllerGenerator.new(directory: 'root').run
79
+ Log.newline
80
+ end
81
+
82
+ def initialize_bundle
83
+ Log.info('Creating Gemfile')
84
+ GemfileGenerator.new.run
85
+ Log.newline
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # The nature of Sinatra and Puma means that the config.ru file must initialize
5
+ # certain files. To make Ella work, this must be updated before the server is
6
+ # started.
7
+ # This is essentially a hack to deal with the fact that Sinatra was designed,
8
+ # apparently, to run one single controller file.
9
+ class RackfileGenerator < Generator
10
+ def run(mode = 'development')
11
+ @mode = mode
12
+ Ella.find_root
13
+ Log.info('Generating rack.ru...')
14
+ write
15
+ end
16
+
17
+ private
18
+
19
+ def main_project_file_content
20
+ Ella.abort('Warning: "main.rb" not found. Aborting.') unless File.exist?('main.rb')
21
+ @main_file_content ||= File.open('main.rb').read
22
+ @main_file_content
23
+ end
24
+
25
+ # This looks bad, but adds a good deal of flexibility for the user, should they ever be
26
+ # desperate to change the project name of an already-generated project.
27
+ def project_name
28
+ raw_project_name = main_project_file_content.scan(/class (.*) < Ella::Controller/).first.first
29
+ NameFormatter.new(raw_project_name)
30
+ end
31
+
32
+ def get_controllers
33
+ @controllers = Dir.entries('controllers').select { |e| e =~ /\.rb$/ }.map { |e| e[0..-4] }
34
+ rescue SystemCallError
35
+ make_directory('controllers')
36
+ retry
37
+ end
38
+
39
+ def get_models
40
+ @models = Dir.entries('models').select { |e| e =~ /\.rb$/ }.map { |e| e[0..-4] }
41
+ rescue SystemCallError
42
+ make_directory('models')
43
+ retry
44
+ end
45
+
46
+ def require_all_models
47
+ get_models
48
+ @models && @models.map { |e| "require_relative 'models/#{e}'" }.join("\n")
49
+ end
50
+
51
+ def require_all_controllers
52
+ get_controllers
53
+ @controllers && @controllers.map { |e| "require_relative 'controllers/#{e}'" }.join("\n")
54
+ end
55
+
56
+ def use_all_controllers
57
+ @controllers && @controllers.map { |e| "use #{e.capitalize}Controller" }.join("\n")
58
+ end
59
+
60
+ def write
61
+ File.open('rack.ru', 'w') { |f| f.puts(generate) }
62
+ end
63
+
64
+ def generate
65
+ "# WARNING: This file is autmatically generated by Ella, and may be rewritten\n" \
66
+ "# at any time. Any changes you make here may vanish. The appropriate place for\n" \
67
+ "# general changes is 'main.rb', which will never be overritten by Ella.)\n" \
68
+ "require 'ella'\n\n" \
69
+ "require './main'\n\n" \
70
+ "#{require_all_models}\n" \
71
+ "#{require_all_controllers}\n\n" \
72
+ "#{use_all_controllers}\n\n" \
73
+ "run #{project_name.pascal_case}\n"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ella
4
+ # Generates views for Ella projects.
5
+ class ViewGenerator < Generator
6
+ def run
7
+ Ella.find_root
8
+ make_directory
9
+ @path = "views/#{target_name}#{target_name ? '/' : ''}"
10
+ create_subdirectory if target_name && !Dir.exist?(@path)
11
+ create_files
12
+ copy_templates
13
+ end
14
+
15
+ private
16
+
17
+ def full_filename(filename)
18
+ "#{@path}#{filename}.erb"
19
+ end
20
+
21
+ def create_file(filename)
22
+ Ella::Template.new(full_filename(filename), generic_template: :blank).write
23
+ end
24
+
25
+ def copy_template(filename)
26
+ Ella::Template.new(full_filename(filename), template_vars: @template_vars).write
27
+ end
28
+
29
+ def copy_templates
30
+ @templates.each { |filename| copy_template(filename) } if @templates
31
+ end
32
+
33
+ def create_files
34
+ @files.each { |filename| create_file(filename) } if @files
35
+ end
36
+
37
+ def create_subdirectory
38
+ Log.create(@path)
39
+ Dir.mkdir(@path)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'colorize'
5
+
6
+ module Ella
7
+ # A convenient minor reworking of Ruby's logger.
8
+ class Log
9
+ # The first three methods do not have timestamps and may be colour coded.
10
+ # They should not be anything less trivial than a heads up for the user.
11
+ def self.error(message)
12
+ say('ERROR: '.red + message.bold)
13
+ end
14
+
15
+ def self.newline
16
+ say("\n")
17
+ end
18
+
19
+ def self.info(message)
20
+ say(" ELLA: ".bold.blue + message.split(' ').map(&:capitalize).join(' ').bold)
21
+ end
22
+
23
+ def self.yell(message)
24
+ say(message.bold.blue)
25
+ end
26
+
27
+ def self.exit_message(message)
28
+ say(message.bold.yellow)
29
+ end
30
+
31
+ # Misc. It would be best to phase this out in the long run.
32
+ def self.say(message)
33
+ puts message
34
+ end
35
+
36
+ def self.unknown(message)
37
+ log_error.unknown(message)
38
+ end
39
+
40
+ def self.fatal(message)
41
+ say('FATAL: '.red + message.bold)
42
+ end
43
+
44
+ def self.warn(message)
45
+ log_info.warn(message)
46
+ end
47
+
48
+ def self.debug(message)
49
+ log_info.info(message)
50
+ end
51
+
52
+ def self.create(message)
53
+ output_str = ' ' * 8 + 'CREATE '.green.bold + message
54
+ say(output_str)
55
+ end
56
+
57
+ def self.destroy(message)
58
+ output_str = ' ' * 8 + 'DESTROY '.red.bold + message
59
+ say(output_str)
60
+ end
61
+
62
+ def self.log_info
63
+ Logger.new(STDOUT)
64
+ end
65
+
66
+ def self.log_error
67
+ Logger.new(STDERR)
68
+ end
69
+ end
70
+ end