katapult 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +11 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +122 -0
  9. data/Rakefile +14 -0
  10. data/bin/katapult +44 -0
  11. data/features/binary.feature +48 -0
  12. data/features/configuration.feature +24 -0
  13. data/features/katapult.feature +201 -0
  14. data/features/model.feature +203 -0
  15. data/features/navigation.feature +80 -0
  16. data/features/step_definitions/db_steps.rb +8 -0
  17. data/features/step_definitions/file_steps.rb +14 -0
  18. data/features/step_definitions/katapult_steps.rb +14 -0
  19. data/features/step_definitions/rails_steps.rb +44 -0
  20. data/features/step_definitions/test_steps.rb +7 -0
  21. data/features/support/env.rb +16 -0
  22. data/features/wui.feature +319 -0
  23. data/katapult.gemspec +35 -0
  24. data/katapult.png +0 -0
  25. data/lib/generators/katapult/basics/basics_generator.rb +95 -0
  26. data/lib/generators/katapult/basics/templates/Gemfile +76 -0
  27. data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application.css.sass +6 -0
  28. data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_all.css.sass +4 -0
  29. data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_items.css.sass +11 -0
  30. data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_layout.css.sass +26 -0
  31. data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_navigation.css.sass +11 -0
  32. data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_tools.css.sass +12 -0
  33. data/lib/generators/katapult/basics/templates/config/database.sample.yml +16 -0
  34. data/lib/generators/katapult/basics/templates/config/database.yml +13 -0
  35. data/lib/generators/katapult/basics/templates/config/spring.rb +3 -0
  36. data/lib/generators/katapult/basics/templates/features/support/env-custom.rb +3 -0
  37. data/lib/generators/katapult/basics/templates/features/support/paths.rb +47 -0
  38. data/lib/generators/katapult/cucumber_features/cucumber_features_generator.rb +23 -0
  39. data/lib/generators/katapult/cucumber_features/templates/feature.feature +59 -0
  40. data/lib/generators/katapult/haml/haml_generator.rb +90 -0
  41. data/lib/generators/katapult/haml/templates/_form.html.haml +38 -0
  42. data/lib/generators/katapult/haml/templates/app/views/layouts/application.html.haml +25 -0
  43. data/lib/generators/katapult/haml/templates/custom_action.html.haml +5 -0
  44. data/lib/generators/katapult/haml/templates/edit.html.haml +4 -0
  45. data/lib/generators/katapult/haml/templates/index.html.haml +29 -0
  46. data/lib/generators/katapult/haml/templates/new.html.haml +4 -0
  47. data/lib/generators/katapult/haml/templates/show.html.haml +41 -0
  48. data/lib/generators/katapult/install/install_generator.rb +14 -0
  49. data/lib/generators/katapult/install/templates/lib/katapult/application_model.rb +18 -0
  50. data/lib/generators/katapult/model/model_generator.rb +59 -0
  51. data/lib/generators/katapult/model/templates/app/models/shared/does_flag.rb +32 -0
  52. data/lib/generators/katapult/model/templates/model.rb +21 -0
  53. data/lib/generators/katapult/model_specs/model_specs_generator.rb +51 -0
  54. data/lib/generators/katapult/model_specs/templates/model_spec.rb +34 -0
  55. data/lib/generators/katapult/navigation/navigation_generator.rb +25 -0
  56. data/lib/generators/katapult/navigation/templates/app/models/navigation.rb +12 -0
  57. data/lib/generators/katapult/transform/transform_generator.rb +47 -0
  58. data/lib/generators/katapult/w_u_i/templates/_route.rb +13 -0
  59. data/lib/generators/katapult/w_u_i/templates/controller.rb +106 -0
  60. data/lib/generators/katapult/w_u_i/w_u_i_generator.rb +57 -0
  61. data/lib/katapult.rb +5 -0
  62. data/lib/katapult/action.rb +44 -0
  63. data/lib/katapult/application_model.rb +45 -0
  64. data/lib/katapult/attribute.rb +83 -0
  65. data/lib/katapult/element.rb +72 -0
  66. data/lib/katapult/generator.rb +28 -0
  67. data/lib/katapult/model.rb +33 -0
  68. data/lib/katapult/navigation.rb +22 -0
  69. data/lib/katapult/parser.rb +39 -0
  70. data/lib/katapult/util.rb +16 -0
  71. data/lib/katapult/version.rb +3 -0
  72. data/lib/katapult/wui.rb +77 -0
  73. data/script/console +16 -0
  74. data/spec/action_spec.rb +44 -0
  75. data/spec/attribute_spec.rb +48 -0
  76. data/spec/model_spec.rb +18 -0
  77. data/spec/spec_helper.rb +19 -0
  78. data/spec/util_spec.rb +23 -0
  79. data/spec/wui_spec.rb +49 -0
  80. metadata +253 -0
@@ -0,0 +1,72 @@
1
+ # The base class for all katapult elements to inherit from.
2
+
3
+ # Every katapult element has a name which is a String. All options passed will
4
+ # be mapped to attributes. Afterwards, the optional block will be yielded with
5
+ # self.
6
+
7
+ module Katapult
8
+ class Element
9
+
10
+ UnknownOptionError = Class.new(StandardError)
11
+
12
+ attr_accessor :name, :options
13
+ attr_reader :application_model
14
+
15
+ # Improve semantics in element classes
16
+ class << self
17
+ alias_method :options, :attr_accessor
18
+ end
19
+
20
+
21
+ def initialize(name, options = {})
22
+ self.name = name.to_s
23
+ self.options = options
24
+
25
+ set_attributes(options)
26
+
27
+ yield(self) if block_given?
28
+ end
29
+
30
+ def set_application_model(app_model)
31
+ @application_model = app_model
32
+ end
33
+
34
+ def name(kind = nil)
35
+ human_name = @name.downcase
36
+ machine_name = @name.underscore
37
+
38
+ case kind.to_s
39
+ when 'symbol' then ":#{machine_name}"
40
+ when 'symbols' then ":#{machine_name.pluralize}"
41
+ when 'variable' then machine_name
42
+ when 'variables' then machine_name.pluralize
43
+ when 'ivar' then "@#{machine_name}"
44
+ when 'ivars' then "@#{machine_name.pluralize}"
45
+ when 'human_plural' then human_name.pluralize
46
+ when 'human' then human_name
47
+ when 'class' then machine_name.classify
48
+ when 'classes' then machine_name.classify.pluralize
49
+ else
50
+ @name
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # Map options to attributes.
57
+ # Example: set_attributes(foo: 123) sets the :foo attribute to 123 (via
58
+ # #foo=) and raises UnknownOptionError if the attribute does not exist.
59
+ def set_attributes(options)
60
+ options.each_pair do |option, value|
61
+ setter = "#{option}="
62
+
63
+ if respond_to? setter
64
+ send(setter, value)
65
+ else
66
+ raise UnknownOptionError, "#{self.class.name} does not support option #{option.inspect}."
67
+ end
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,28 @@
1
+ # The katapult generator base class, slightly adapted from Rails generators.
2
+
3
+ require 'rails/generators'
4
+
5
+ module Katapult
6
+ class Generator < Rails::Generators::NamedBase
7
+
8
+ attr_accessor :element
9
+
10
+ def initialize(element)
11
+ self.element = element
12
+
13
+ super([element.name], {}, {}) # args, opts, config
14
+ end
15
+
16
+ private
17
+
18
+ def app_name
19
+ File.basename(Dir.pwd)
20
+ end
21
+
22
+ def render_partial(template_path, given_binding = nil)
23
+ path = File.join(self.class.source_root, template_path)
24
+ ERB.new(::File.binread(path), nil, '%').result(given_binding || binding)
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ # Models a Rails model.
2
+
3
+ require 'katapult/element'
4
+ require 'katapult/attribute'
5
+ require 'generators/katapult/model/model_generator'
6
+
7
+ module Katapult
8
+ class Model < Element
9
+
10
+ UnknownAttributeError = Class.new(StandardError)
11
+
12
+ attr_accessor :attrs
13
+
14
+ def initialize(*args)
15
+ self.attrs = []
16
+
17
+ super
18
+ end
19
+
20
+ def attr(attr_name, options = {})
21
+ attrs << Attribute.new(attr_name, options)
22
+ end
23
+
24
+ def label_attr
25
+ attrs.first
26
+ end
27
+
28
+ def render
29
+ Generators::ModelGenerator.new(self).invoke_all
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ # Models a navigation.
2
+
3
+ require 'katapult/element'
4
+ require 'generators/katapult/navigation/navigation_generator'
5
+
6
+ module Katapult
7
+ class Navigation < Element
8
+
9
+ def wuis
10
+ application_model.wuis
11
+ end
12
+
13
+ def section_name(wui)
14
+ wui.model_name(:symbols)
15
+ end
16
+
17
+ def render
18
+ Generators::NavigationGenerator.new(self).invoke_all
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ # This class reads an application model file and turns it into an
2
+ # ApplicationModel instance.
3
+
4
+ require_relative 'application_model'
5
+ require 'katapult/model'
6
+ require 'katapult/wui'
7
+ require 'katapult/navigation'
8
+
9
+ module Katapult
10
+ class Parser
11
+
12
+ def initialize
13
+ self.application_model = Katapult::ApplicationModel.new
14
+ end
15
+
16
+ def parse(path_to_app_model_file)
17
+ instance_eval File.read(path_to_app_model_file), path_to_app_model_file
18
+
19
+ application_model
20
+ end
21
+
22
+ def model(name, options = {}, &block)
23
+ application_model.add_model Model.new(name, options, &block)
24
+ end
25
+
26
+ def wui(name, options = {}, &block)
27
+ application_model.add_wui WUI.new(name, options, &block)
28
+ end
29
+
30
+ def navigation(name)
31
+ application_model.set_navigation Navigation.new(name)
32
+ end
33
+
34
+ private
35
+
36
+ attr_accessor :application_model
37
+
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ # Utility methods.
2
+
3
+ # The Katapult::Util module is used inside the `katapult` script. It should not
4
+ # require any gems in order to prevent version conflicts.
5
+
6
+ module Katapult
7
+ module Util
8
+ extend self
9
+
10
+ def git_commit(message)
11
+ message.gsub! /'/, "" # remove single quotes
12
+ system "git add --all; git commit -m '#{ message }' --author='katapult <katapult@makandra.com>'"
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Katapult
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,77 @@
1
+ # Models a controller, including routes and views. Little more than a container
2
+ # class for the Attribute element.
3
+
4
+ require 'katapult/element'
5
+ require 'katapult/action'
6
+ require 'generators/katapult/w_u_i/w_u_i_generator'
7
+
8
+ module Katapult
9
+ class WUI < Element
10
+
11
+ options :model
12
+ attr_accessor :actions
13
+
14
+ RAILS_ACTIONS = %w[ index show new create edit update destroy ]
15
+ UnknownActionError = Class.new(StandardError)
16
+ UnknownModelError = Class.new(StandardError)
17
+
18
+ def initialize(*args)
19
+ self.actions = []
20
+
21
+ super
22
+ end
23
+
24
+ # DSL
25
+ def action(name, options = {})
26
+ actions << Action.new(:new, options) if name.to_s == 'create'
27
+ actions << Action.new(:edit, options) if name.to_s == 'update'
28
+
29
+ actions << Action.new(name, options)
30
+ end
31
+
32
+ # DSL
33
+ def crud
34
+ %i(index show create update destroy).each &method(:action)
35
+ end
36
+
37
+ def model
38
+ model_name = @model || self.name
39
+ application_model.get_model(model_name) or raise UnknownModelError,
40
+ "Could not find a model named #{model_name}"
41
+ end
42
+
43
+ def custom_actions
44
+ actions.reject { |a| RAILS_ACTIONS.include? a.name }
45
+ end
46
+
47
+ def find_action(action_name)
48
+ actions.find { |a| a.name == action_name.to_s }
49
+ end
50
+
51
+ def path(action, object_name = nil)
52
+ unless action.is_a?(Action)
53
+ not_found_message = "Unknown action '#{action}'"
54
+ action = find_action(action) or raise UnknownActionError, not_found_message
55
+ end
56
+
57
+ member_path = "#{model.name(:variable)}_path"
58
+ collection_path = "#{model.name(:variables)}_path"
59
+
60
+ path = ''
61
+ path << action.name << '_' unless %w[index show destroy].include?(action.name)
62
+ path << (action.member? ? member_path : collection_path)
63
+ path << "(#{object_name})" if object_name
64
+
65
+ path
66
+ end
67
+
68
+ def model_name(kind = nil)
69
+ model.name(kind)
70
+ end
71
+
72
+ def render
73
+ Generators::WUIGenerator.new(self).invoke_all
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This file imitates the Rails console to ease debugging the gem.
4
+ # Run `script/console` from the gem root to start an IRB with the gem loaded.
5
+
6
+ # Remember to require classes before using them,
7
+ # i.e. run `require 'katapult/wui'` before `Katapult::WUI.new :example_name`.
8
+
9
+ irb_options = [
10
+ '-Ilib', # add lib/ to load_path
11
+ '-d', # set $DEBUG = true
12
+ '-rkatapult', # require katapult
13
+ '-f' # don't read ~/.irbrc
14
+ ]
15
+
16
+ exec 'irb', *irb_options
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+ require 'katapult/action'
3
+
4
+ describe Katapult::Action do
5
+
6
+ subject { described_class.new 'action' }
7
+
8
+ describe '#post?' do
9
+ it 'returns true when its method is :post' do
10
+ subject.method = :post
11
+ expect(subject.post?).to be true
12
+ end
13
+
14
+ it 'returns false when its method is :get' do
15
+ subject.method = :get
16
+ expect(subject.post?).to be false
17
+ end
18
+ end
19
+
20
+ describe '#member?' do
21
+ it 'returns true when its scope is :member' do
22
+ subject.scope = :member
23
+ expect(subject.member?).to be true
24
+ end
25
+
26
+ it 'returns false when its scope is :collection' do
27
+ subject.scope = :collection
28
+ expect(subject.member?).to be false
29
+ end
30
+
31
+ it 'returns false when it is an index action' do
32
+ subject = described_class.new :index
33
+ expect(subject.member?).to be false
34
+ end
35
+ end
36
+
37
+ describe '#collection?' do
38
+ it 'returns true when it is an index action' do
39
+ subject = described_class.new :index
40
+ expect(subject.collection?).to be true
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'katapult/attribute'
3
+
4
+ describe Katapult::Attribute do
5
+
6
+ it 'is of type :string by default' do
7
+ expect(described_class.new('name').type).to eql(:string)
8
+ expect(described_class.new('address').type).to eql(:string)
9
+ end
10
+
11
+ it 'requires a default for :flag attributes' do
12
+ expect do
13
+ described_class.new('attr', type: :flag)
14
+ end.to raise_error(Katapult::Attribute::MissingOptionError,
15
+ "The :flag attribute 'attr' requires a default (true or false).")
16
+ end
17
+
18
+ describe '#flag?' do
19
+ it 'returns whether it is of type :flag' do
20
+ expect(described_class.new('attr', type: :flag, default: false).flag?).to be true
21
+ expect(described_class.new('attr', type: :string).flag?).to be false
22
+ end
23
+ end
24
+
25
+ describe 'available types' do
26
+ it 'raises an error if the specified type is not supported' do
27
+ expect do
28
+ described_class.new('attr', type: :undefined)
29
+ end.to raise_error(Katapult::Attribute::UnknownTypeError,
30
+ "Attribute type :undefined is not supported. Use one of #{Katapult::Attribute::TYPES.inspect}."
31
+ )
32
+ end
33
+ end
34
+
35
+ describe 'email attributes' do
36
+ it 'recognizes email attributes' do
37
+ expect(described_class.new('email').type).to eql(:email)
38
+ expect(described_class.new('customer_email').type).to eql(:email)
39
+ expect(described_class.new('name').type).to_not eql(:email)
40
+ end
41
+
42
+ it 'does not overwrite a given type' do
43
+ expect(described_class.new('email', type: :url).type).to eql(:url)
44
+ expect(described_class.new('email_updated_at', type: :datetime).type).to eql(:datetime)
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'katapult/model'
3
+ require 'katapult/attribute'
4
+
5
+ describe Katapult::Model do
6
+
7
+ subject { described_class.new('model') }
8
+
9
+ describe '#label_attr' do
10
+ it 'returns the model’s first attribute' do
11
+ subject.attr('first_attr')
12
+ subject.attr('second_attr')
13
+
14
+ expect(subject.label_attr.name).to eql('first_attr')
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,19 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'pry'
5
+
6
+ # Require some Rails for the specs to pass
7
+ require 'active_support/core_ext/string/inflections'
8
+
9
+ RSpec.configure do |config|
10
+ config.treat_symbols_as_metadata_keys_with_true_values = true
11
+ config.run_all_when_everything_filtered = true
12
+ config.filter_run :focus
13
+
14
+ # Run specs in random order to surface order dependencies. If you find an
15
+ # order dependency and want to debug it, you can fix the order by providing
16
+ # the seed, which is printed after each run.
17
+ # --seed 1234
18
+ config.order = 'random'
19
+ end