katapult 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Guardfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/Rakefile +14 -0
- data/bin/katapult +44 -0
- data/features/binary.feature +48 -0
- data/features/configuration.feature +24 -0
- data/features/katapult.feature +201 -0
- data/features/model.feature +203 -0
- data/features/navigation.feature +80 -0
- data/features/step_definitions/db_steps.rb +8 -0
- data/features/step_definitions/file_steps.rb +14 -0
- data/features/step_definitions/katapult_steps.rb +14 -0
- data/features/step_definitions/rails_steps.rb +44 -0
- data/features/step_definitions/test_steps.rb +7 -0
- data/features/support/env.rb +16 -0
- data/features/wui.feature +319 -0
- data/katapult.gemspec +35 -0
- data/katapult.png +0 -0
- data/lib/generators/katapult/basics/basics_generator.rb +95 -0
- data/lib/generators/katapult/basics/templates/Gemfile +76 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application.css.sass +6 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_all.css.sass +4 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_items.css.sass +11 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_layout.css.sass +26 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_navigation.css.sass +11 -0
- data/lib/generators/katapult/basics/templates/app/assets/stylesheets/application/blocks/_tools.css.sass +12 -0
- data/lib/generators/katapult/basics/templates/config/database.sample.yml +16 -0
- data/lib/generators/katapult/basics/templates/config/database.yml +13 -0
- data/lib/generators/katapult/basics/templates/config/spring.rb +3 -0
- data/lib/generators/katapult/basics/templates/features/support/env-custom.rb +3 -0
- data/lib/generators/katapult/basics/templates/features/support/paths.rb +47 -0
- data/lib/generators/katapult/cucumber_features/cucumber_features_generator.rb +23 -0
- data/lib/generators/katapult/cucumber_features/templates/feature.feature +59 -0
- data/lib/generators/katapult/haml/haml_generator.rb +90 -0
- data/lib/generators/katapult/haml/templates/_form.html.haml +38 -0
- data/lib/generators/katapult/haml/templates/app/views/layouts/application.html.haml +25 -0
- data/lib/generators/katapult/haml/templates/custom_action.html.haml +5 -0
- data/lib/generators/katapult/haml/templates/edit.html.haml +4 -0
- data/lib/generators/katapult/haml/templates/index.html.haml +29 -0
- data/lib/generators/katapult/haml/templates/new.html.haml +4 -0
- data/lib/generators/katapult/haml/templates/show.html.haml +41 -0
- data/lib/generators/katapult/install/install_generator.rb +14 -0
- data/lib/generators/katapult/install/templates/lib/katapult/application_model.rb +18 -0
- data/lib/generators/katapult/model/model_generator.rb +59 -0
- data/lib/generators/katapult/model/templates/app/models/shared/does_flag.rb +32 -0
- data/lib/generators/katapult/model/templates/model.rb +21 -0
- data/lib/generators/katapult/model_specs/model_specs_generator.rb +51 -0
- data/lib/generators/katapult/model_specs/templates/model_spec.rb +34 -0
- data/lib/generators/katapult/navigation/navigation_generator.rb +25 -0
- data/lib/generators/katapult/navigation/templates/app/models/navigation.rb +12 -0
- data/lib/generators/katapult/transform/transform_generator.rb +47 -0
- data/lib/generators/katapult/w_u_i/templates/_route.rb +13 -0
- data/lib/generators/katapult/w_u_i/templates/controller.rb +106 -0
- data/lib/generators/katapult/w_u_i/w_u_i_generator.rb +57 -0
- data/lib/katapult.rb +5 -0
- data/lib/katapult/action.rb +44 -0
- data/lib/katapult/application_model.rb +45 -0
- data/lib/katapult/attribute.rb +83 -0
- data/lib/katapult/element.rb +72 -0
- data/lib/katapult/generator.rb +28 -0
- data/lib/katapult/model.rb +33 -0
- data/lib/katapult/navigation.rb +22 -0
- data/lib/katapult/parser.rb +39 -0
- data/lib/katapult/util.rb +16 -0
- data/lib/katapult/version.rb +3 -0
- data/lib/katapult/wui.rb +77 -0
- data/script/console +16 -0
- data/spec/action_spec.rb +44 -0
- data/spec/attribute_spec.rb +48 -0
- data/spec/model_spec.rb +18 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/util_spec.rb +23 -0
- data/spec/wui_spec.rb +49 -0
- metadata +253 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe <%= model.name :class %> do
|
4
|
+
|
5
|
+
describe '#to_s' do
|
6
|
+
<%- if model.label_attr -%>
|
7
|
+
it 'returns the #<%= model.label_attr.name %> attribute' do
|
8
|
+
subject.<%= model.label_attr.name %> = <%= model.label_attr.test_value.inspect %>
|
9
|
+
expect(subject.to_s).to eql(<%= model.label_attr.test_value.to_s.inspect %>)
|
10
|
+
end
|
11
|
+
<%- else -%>
|
12
|
+
it 'returns its class name with its id' do
|
13
|
+
subject.id = 17
|
14
|
+
expect(subject.to_s).to eql("<%= model.name %>#17")
|
15
|
+
end
|
16
|
+
<%- end -%>
|
17
|
+
end
|
18
|
+
<% specable_attrs.each do |attr| -%>
|
19
|
+
|
20
|
+
describe '#<%= attr.name %>' do
|
21
|
+
<%- if attr.assignable_values.present? -%>
|
22
|
+
it { is_expected.to allow_value(<%= assignable_value_for(attr).inspect %>).for(:<%= attr.name %>) }
|
23
|
+
it { is_expected.to_not allow_value(<%= unassignable_value_for(attr).inspect %>).for(:<%= attr.name %>) }
|
24
|
+
<%- end -%>
|
25
|
+
<%- unless attr.default.nil? -%>
|
26
|
+
|
27
|
+
it 'has a default' do
|
28
|
+
expect( subject.<%= attr.name %> ).to eql(<%= attr.default.inspect %>)
|
29
|
+
end
|
30
|
+
<%- end -%>
|
31
|
+
end
|
32
|
+
<% end -%>
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Generate navigation.
|
2
|
+
|
3
|
+
require 'katapult/generator'
|
4
|
+
|
5
|
+
module Katapult
|
6
|
+
module Generators
|
7
|
+
class NavigationGenerator < Katapult::Generator
|
8
|
+
|
9
|
+
desc 'Generate the navigation'
|
10
|
+
source_root File.expand_path('../templates', __FILE__)
|
11
|
+
|
12
|
+
|
13
|
+
def create_navigation
|
14
|
+
template 'app/models/navigation.rb'
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def navigation
|
20
|
+
@element
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Navigation
|
2
|
+
include Navy::Description
|
3
|
+
|
4
|
+
navigation :<%= name.downcase %> do
|
5
|
+
<% navigation.wuis.each do |wui| -%>
|
6
|
+
<%- if wui.find_action(:index) -%>
|
7
|
+
section <%= navigation.section_name(wui) %>, <%= wui.model_name(:human_plural).titlecase.inspect %>, <%= wui.path(:index) %>
|
8
|
+
<%- end -%>
|
9
|
+
<% end -%>
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Generate files and directories from an application model file. Afterwards do
|
2
|
+
# any setup left necessary (e.g. updating the database).
|
3
|
+
|
4
|
+
# The application model transformation is split into two parts:
|
5
|
+
# 1) parse the model into an object-based representation
|
6
|
+
# 2) render the parsed model into code
|
7
|
+
|
8
|
+
require 'katapult/parser'
|
9
|
+
|
10
|
+
module Katapult
|
11
|
+
class TransformGenerator < Rails::Generators::Base
|
12
|
+
desc 'Transform the katapult application model'
|
13
|
+
|
14
|
+
argument :path, required: true, type: :string,
|
15
|
+
description: 'The path to the application model file'
|
16
|
+
|
17
|
+
|
18
|
+
def transform_application_model
|
19
|
+
say_status :parse, path
|
20
|
+
@app_model = Katapult::Parser.new.parse(path)
|
21
|
+
|
22
|
+
say_status :render, "into #{application_name}"
|
23
|
+
@app_model.render
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_root_route
|
27
|
+
root_wui = @app_model.wuis.find do |wui|
|
28
|
+
wui.find_action :index
|
29
|
+
end
|
30
|
+
|
31
|
+
route "root '#{ root_wui.model_name(:variables) }#index'" if root_wui
|
32
|
+
end
|
33
|
+
|
34
|
+
def migrate
|
35
|
+
run 'bin/rake db:drop:all &> /dev/null'
|
36
|
+
run 'bin/rake db:create db:migrate RAILS_ENV=development'
|
37
|
+
run 'bin/rake db:create db:migrate RAILS_ENV=test'
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def application_name
|
43
|
+
File.basename(Dir.pwd)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
% rails_actions = wui.actions - wui.custom_actions
|
2
|
+
resources <%= model_name(:symbols) %>, only: <%= rails_actions.map(&:name).map(&:to_sym) %> do
|
3
|
+
member do
|
4
|
+
% wui.custom_actions.select(&:member?).each do |action|
|
5
|
+
<%= action.method %> '<%= action.name %>'
|
6
|
+
% end
|
7
|
+
end
|
8
|
+
collection do
|
9
|
+
% wui.custom_actions.select(&:collection?).each do |action|
|
10
|
+
<%= action.method %> '<%= action.name %>'
|
11
|
+
% end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class <%= model_name(:classes) %>Controller < ApplicationController
|
2
|
+
<%- if navigation -%>
|
3
|
+
|
4
|
+
before_filter :set_section
|
5
|
+
<%- end -%>
|
6
|
+
<% if wui.find_action :index -%>
|
7
|
+
|
8
|
+
def index
|
9
|
+
<%= method_name(:load_collection) %>
|
10
|
+
end
|
11
|
+
<% end -%>
|
12
|
+
<% if wui.find_action :show -%>
|
13
|
+
|
14
|
+
def show
|
15
|
+
<%= method_name(:load_object) %>
|
16
|
+
end
|
17
|
+
<% end -%>
|
18
|
+
<% if wui.find_action :new -%>
|
19
|
+
|
20
|
+
def new
|
21
|
+
<%= method_name(:build) %>
|
22
|
+
end
|
23
|
+
<% end -%>
|
24
|
+
<% if wui.find_action :create -%>
|
25
|
+
|
26
|
+
def create
|
27
|
+
<%= method_name(:build) %>
|
28
|
+
<%= method_name(:save) %> or render :new
|
29
|
+
end
|
30
|
+
<% end -%>
|
31
|
+
<% if wui.find_action :edit -%>
|
32
|
+
|
33
|
+
def edit
|
34
|
+
<%= method_name(:load_object) %>
|
35
|
+
<%= method_name(:build) %>
|
36
|
+
end
|
37
|
+
<% end -%>
|
38
|
+
<% if wui.find_action :update -%>
|
39
|
+
|
40
|
+
def update
|
41
|
+
<%= method_name(:load_object) %>
|
42
|
+
<%= method_name(:build) %>
|
43
|
+
<%= method_name(:save) %> or render :edit
|
44
|
+
end
|
45
|
+
<% end -%>
|
46
|
+
<% if wui.find_action :destroy -%>
|
47
|
+
|
48
|
+
def destroy
|
49
|
+
<%= method_name(:load_object) %>
|
50
|
+
<%= model_name(:ivar) %>.destroy
|
51
|
+
redirect_to <%= wui.path(:index) %>
|
52
|
+
end
|
53
|
+
<% end -%>
|
54
|
+
<% wui.custom_actions.each do |action| -%>
|
55
|
+
|
56
|
+
def <%= action.name %>
|
57
|
+
<% if action.member? -%>
|
58
|
+
<%= method_name(:load_object) %>
|
59
|
+
<%- unless action.get? -%>
|
60
|
+
redirect_to <%= model_name(:ivar) %>
|
61
|
+
<%- end -%>
|
62
|
+
<% elsif action.collection? -%>
|
63
|
+
<%= method_name(:load_collection) %>
|
64
|
+
<%- unless action.get? -%>
|
65
|
+
redirect_to <%= wui.path(:index) %>
|
66
|
+
<%- end -%>
|
67
|
+
<% end -%>
|
68
|
+
end
|
69
|
+
<% end -%>
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def <%= method_name(:load_collection) %>
|
74
|
+
<%= model_name(:ivars) %> ||= <%= method_name(:scope) %>.to_a
|
75
|
+
end
|
76
|
+
|
77
|
+
def <%= method_name(:load_object) %>
|
78
|
+
<%= model_name(:ivar) %> ||= <%= method_name(:scope) %>.find(params[:id])
|
79
|
+
end
|
80
|
+
|
81
|
+
def <%= method_name(:build) %>
|
82
|
+
<%= model_name(:ivar) %> ||= <%= method_name(:scope) %>.build
|
83
|
+
<%= model_name(:ivar) %>.attributes = <%= method_name(:params) %>
|
84
|
+
end
|
85
|
+
|
86
|
+
def <%= method_name(:save) %>
|
87
|
+
if <%= model_name(:ivar) %>.save
|
88
|
+
redirect_to <%= model_name(:ivar) %>
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def <%= method_name(:params) %>
|
93
|
+
<%= method_name(:params) %> = params[:<%= model_name(:variable) %>]
|
94
|
+
<%= method_name(:params) %> ? <%= method_name(:params) %>.permit(<%= wui.model.attrs.map(&:name).map(&:to_sym) %>) : {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def <%= method_name(:scope) %>
|
98
|
+
<%= model_name(:class_name) %>.scoped
|
99
|
+
end
|
100
|
+
|
101
|
+
<%- if navigation -%>
|
102
|
+
def set_section
|
103
|
+
in_sections <%= navigation.section_name(wui) %>
|
104
|
+
end
|
105
|
+
<%- end -%>
|
106
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Generate a controller, routes and view files for a WUI.
|
2
|
+
|
3
|
+
require 'katapult/generator'
|
4
|
+
require 'generators/katapult/haml/haml_generator'
|
5
|
+
|
6
|
+
module Katapult
|
7
|
+
module Generators
|
8
|
+
class WUIGenerator < Katapult::Generator
|
9
|
+
|
10
|
+
desc 'Generate a Web User Interface'
|
11
|
+
|
12
|
+
check_class_collision suffix: 'Controller'
|
13
|
+
source_root File.expand_path('../templates', __FILE__)
|
14
|
+
|
15
|
+
|
16
|
+
def create_controller_file
|
17
|
+
template 'controller.rb', File.join('app', 'controllers', "#{ model_name(:variables) }_controller.rb")
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_route
|
21
|
+
route render_partial('_route.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate_views
|
25
|
+
Generators::HamlGenerator.new(wui).invoke_all
|
26
|
+
end
|
27
|
+
|
28
|
+
no_tasks do
|
29
|
+
def method_name(name)
|
30
|
+
case name
|
31
|
+
when :load_collection then "load_#{model_name :variables}"
|
32
|
+
when :load_object then "load_#{model_name :variable}"
|
33
|
+
when :build then "build_#{model_name :variable}"
|
34
|
+
when :save then "save_#{model_name :variable}"
|
35
|
+
when :params then "#{model_name :variable}_params"
|
36
|
+
when :scope then "#{model_name :variable}_scope"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def model_name(kind = nil)
|
41
|
+
wui.model_name(kind)
|
42
|
+
end
|
43
|
+
|
44
|
+
def navigation
|
45
|
+
wui.application_model.navigation
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def wui
|
52
|
+
@element
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/katapult.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Models a controller action. To be used within the block of a WUI.
|
2
|
+
|
3
|
+
require 'katapult/element'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
require 'active_support/core_ext/string/inquiry'
|
6
|
+
|
7
|
+
module Katapult
|
8
|
+
class Action < Element
|
9
|
+
|
10
|
+
options :method, :scope
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
super
|
14
|
+
|
15
|
+
self.scope ||= (name == 'index') ? :collection : :member
|
16
|
+
set_method
|
17
|
+
end
|
18
|
+
|
19
|
+
delegate :post?, :get?, :put?, to: :method_inquiry
|
20
|
+
delegate :member?, :collection?, to: :scope_inquiry
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def method_inquiry
|
25
|
+
@method.to_s.inquiry
|
26
|
+
end
|
27
|
+
|
28
|
+
def scope_inquiry
|
29
|
+
@scope.to_s.inquiry
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_method
|
33
|
+
self.method ||= case name
|
34
|
+
when 'create', 'update'
|
35
|
+
:post
|
36
|
+
when 'destroy'
|
37
|
+
:delete
|
38
|
+
else # index, show, new, edit + custom actions
|
39
|
+
:get
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Internal representation of an application model file.
|
2
|
+
|
3
|
+
module Katapult
|
4
|
+
class ApplicationModel
|
5
|
+
|
6
|
+
attr_reader :models, :wuis, :navigation
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@models = []
|
10
|
+
@wuis = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_model(model)
|
14
|
+
model.set_application_model(self)
|
15
|
+
@models << model
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_model(name)
|
19
|
+
models.find { |m| m.name == name }
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_wui(wui)
|
23
|
+
wui.set_application_model(self)
|
24
|
+
@wuis << wui
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_wui(name)
|
28
|
+
wuis.find { |w| w.name == name }
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_navigation(navigation)
|
32
|
+
navigation.set_application_model(self)
|
33
|
+
@navigation = navigation
|
34
|
+
end
|
35
|
+
|
36
|
+
# ---
|
37
|
+
|
38
|
+
def render
|
39
|
+
models.each &:render
|
40
|
+
wuis.each &:render
|
41
|
+
navigation.render if navigation
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Models a model attribute. To be used within the block of a model element.
|
2
|
+
|
3
|
+
require 'katapult/element'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
require 'active_support/core_ext/string/inquiry'
|
6
|
+
require 'zlib'
|
7
|
+
|
8
|
+
module Katapult
|
9
|
+
class Attribute < Element
|
10
|
+
|
11
|
+
options :type, :default, :assignable_values, :allow_blank
|
12
|
+
|
13
|
+
UnknownTypeError = Class.new(StandardError)
|
14
|
+
MissingOptionError = Class.new(StandardError)
|
15
|
+
TYPES = %i(string email url integer money text markdown flag datetime)
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
super
|
19
|
+
|
20
|
+
self.type ||= :email if name.to_s =~ /email/
|
21
|
+
self.type ||= :string
|
22
|
+
|
23
|
+
validate
|
24
|
+
end
|
25
|
+
|
26
|
+
delegate :flag?, to: :type_inquiry
|
27
|
+
|
28
|
+
def has_defaults?
|
29
|
+
default and not [flag?, assignable_values].any?
|
30
|
+
end
|
31
|
+
|
32
|
+
def for_migration
|
33
|
+
db_type = case type
|
34
|
+
when :email, :url
|
35
|
+
'string'
|
36
|
+
when :flag
|
37
|
+
'boolean'
|
38
|
+
when :money
|
39
|
+
'decimal{10,2}' # {precision,scale} = total digits, decimal places
|
40
|
+
else
|
41
|
+
type
|
42
|
+
end
|
43
|
+
|
44
|
+
"#{name}:#{db_type}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_value
|
48
|
+
if assignable_values
|
49
|
+
assignable_values.first
|
50
|
+
|
51
|
+
else
|
52
|
+
case type
|
53
|
+
when :string then "#{name}-string"
|
54
|
+
when :email then "#{name}@example.com"
|
55
|
+
when :url then "#{name}.example.com"
|
56
|
+
when :text then "#{name}-text"
|
57
|
+
|
58
|
+
# Deterministically generate a value from the attribute's name
|
59
|
+
when :integer then Zlib.crc32(name).modulo(1000)
|
60
|
+
when :money then Zlib.crc32(name).modulo(1000) / 100.0
|
61
|
+
when :datetime then Time.at(Zlib.crc32(name))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def type_inquiry
|
69
|
+
@type.to_s.inquiry
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate
|
73
|
+
TYPES.include?(type) or raise UnknownTypeError,
|
74
|
+
"Attribute type :#{type} is not supported. Use one of #{TYPES.inspect}."
|
75
|
+
|
76
|
+
if flag? and default.nil?
|
77
|
+
raise MissingOptionError,
|
78
|
+
"The :flag attribute '#{name}' requires a default (true or false)."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|