picatrix 0.5.0 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +17 -37
- data/Rakefile +35 -0
- data/bin/console +8 -4
- data/bin/post-receive.sh +3 -0
- data/bin/setup +7 -1
- data/generated/zoo.dot +37 -0
- data/generated/zoo.dot.png +0 -0
- data/{lib/picatrix/generated/view_submissions.rb → generated/zoo/resources/animals.rb} +10 -13
- data/generated/zoo/support/mason.rb +114 -0
- data/generated/zoo/support/namespaces.json +7 -0
- data/generated/zoo/support/picatrix.mason.pb.rb +95 -0
- data/generated/zoo/support/zoo.pb.rb +76 -0
- data/generated/zoo/support/zoo.proto +65 -0
- data/generated/zoo/zoo.rb +116 -0
- data/lib/picatrix.rb +25 -10
- data/lib/picatrix/buffy.rb +38 -40
- data/lib/picatrix/cruddy.rb +18 -16
- data/lib/picatrix/jenny.rb +31 -5
- data/lib/picatrix/link_relation_index.rb +1 -1
- data/lib/picatrix/pacman.rb +12 -6
- data/lib/picatrix/protoc.rb +7 -0
- data/lib/picatrix/prototyper.rb +56 -0
- data/lib/picatrix/routes.rb +40 -11
- data/{definitions/mason.proto → lib/picatrix/support/picatrix.mason.proto} +16 -13
- data/lib/picatrix/templates/app.proto +42 -0
- data/lib/picatrix/templates/app.rb +24 -136
- data/lib/picatrix/templates/mason.rb +114 -0
- data/lib/picatrix/templates/namespaces.json +7 -0
- data/lib/picatrix/templates/type.rb +6 -3
- data/lib/picatrix/transform_to_hash.rb +2 -2
- data/lib/picatrix/version.rb +1 -1
- data/notes.md +34 -0
- metadata +20 -10
- data/definitions/app.proto +0 -47
- data/lib/picatrix/fixtures/view_submission.rb +0 -19
- data/lib/picatrix/generated/app.pb.rb +0 -79
- data/lib/picatrix/generated/app.rb +0 -209
- data/lib/picatrix/generated/mason.pb.rb +0 -90
- data/lib/picatrix/generated/mason/namespaces.json +0 -7
data/lib/picatrix/jenny.rb
CHANGED
@@ -26,30 +26,56 @@ module Picatrix
|
|
26
26
|
@meta = {}
|
27
27
|
|
28
28
|
if File.exist?(
|
29
|
-
|
29
|
+
namespaces_path = "generated/#{app_name}/support/namespaces.json"
|
30
30
|
) #TODO this should be coming in from graph instead
|
31
31
|
@namespaces = JSON.parse(
|
32
32
|
File.read(
|
33
|
-
|
33
|
+
namespaces_path
|
34
34
|
)
|
35
35
|
)
|
36
36
|
@prefix = "#{@namespaces["@namespaces"].keys.first}:"
|
37
37
|
else
|
38
|
-
puts "no generated/
|
38
|
+
puts "no generated/#{app_name}/support/namespaces.json file"
|
39
39
|
@namespaces = {}
|
40
40
|
@prefix = ""
|
41
41
|
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def just_do_it
|
45
|
+
setup_framework_for(app_name)
|
46
|
+
generate_sinatra_of(app_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def setup_framework_for(app_name)
|
50
|
+
template(
|
51
|
+
'templates/mason.rb',
|
52
|
+
File.join("../../generated/#{app_name}/support/mason.rb")
|
53
|
+
)
|
54
|
+
|
55
|
+
Protoc.to_ruby(app_name)
|
56
|
+
|
57
|
+
begin
|
58
|
+
jenny_path = File.dirname( __FILE__ )
|
59
|
+
$:.unshift File.expand_path( File.join( jenny_path, "..", "..", "generated" ) )
|
60
|
+
$:.unshift File.expand_path( File.join( jenny_path, "..", "..", "generated", "#{app_name}" ) )
|
61
|
+
$:.unshift File.expand_path( File.join( jenny_path, "..", "..", "generated", "#{app_name}", "support" ) )
|
62
|
+
|
63
|
+
require_all("./generated/#{app_name}/**/*.rb")
|
64
|
+
rescue LoadError => e
|
65
|
+
puts "#{e}"
|
66
|
+
end
|
42
67
|
|
43
68
|
@control_templates = Cruddy.new(@edges).controls
|
69
|
+
@resources = Prototyper.new(@nodes, {}, app_name).resources
|
44
70
|
@routes = Routes.new(@edges, @control_templates)
|
45
71
|
# make this available to the app scope
|
46
72
|
@root_path = @routes.root_path
|
47
73
|
end
|
48
74
|
|
49
|
-
def
|
75
|
+
def generate_sinatra_of(app_name)
|
50
76
|
template(
|
51
77
|
'templates/app.rb',
|
52
|
-
File.join("generated/#{app_name}.rb")
|
78
|
+
File.join("../../generated/#{app_name}/#{app_name}.rb")
|
53
79
|
)
|
54
80
|
end
|
55
81
|
|
@@ -5,7 +5,7 @@ module Picatrix
|
|
5
5
|
@as_hash = {
|
6
6
|
link_relations: edges.flat_map do |e|
|
7
7
|
source = e.first.at(0)
|
8
|
-
link_relation = e.first.at(1)[:link_relation]
|
8
|
+
link_relation = e.first.at(1)[:link_relation]
|
9
9
|
|
10
10
|
{
|
11
11
|
link_relation.to_sym => "#{source} -(#{e.first.at(1)[:method].upcase})-> #{e.first.at(1)[:target]}"
|
data/lib/picatrix/pacman.rb
CHANGED
@@ -2,21 +2,27 @@ module Picatrix
|
|
2
2
|
# eats dots :)
|
3
3
|
# and gives you back nodes and edges
|
4
4
|
class Pacman
|
5
|
-
attr_accessor :hash
|
6
5
|
attr_accessor :nodes, :edges
|
7
6
|
|
8
7
|
def initialize(file_name)
|
9
|
-
|
8
|
+
hash = TransformToHash.new.apply(
|
10
9
|
DigraphParser.new.parse(
|
11
10
|
File.read(file_name)
|
12
11
|
)
|
13
12
|
).flat_map(&:entries).
|
14
13
|
group_by(&:first).
|
15
|
-
map {|k,v| Hash[k, v.map(&:last)]}
|
14
|
+
map {|k,v| Hash[k, v.map(&:last)]}.
|
15
|
+
reduce({}, :merge)
|
16
16
|
|
17
|
-
|
18
|
-
@
|
19
|
-
|
17
|
+
@nodes = hash[:node].reduce({}, :merge)
|
18
|
+
@edges = hash[:edge]
|
19
|
+
end
|
20
|
+
|
21
|
+
def graph_hash
|
22
|
+
{
|
23
|
+
node: nodes,
|
24
|
+
edge: edges
|
25
|
+
}
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'protobuf'
|
2
|
+
require 'faker'
|
3
|
+
|
4
|
+
module Picatrix
|
5
|
+
class Prototyper
|
6
|
+
include Thor::Base
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
attr_accessor :destination_stack, :options,
|
10
|
+
:resources
|
11
|
+
|
12
|
+
source_root File.dirname(__FILE__)
|
13
|
+
|
14
|
+
PROTOBUF_FIXTURES = {
|
15
|
+
Protobuf::Field::Int32Field => "Faker::Number.number(6)",
|
16
|
+
Protobuf::Field::StringField => "Faker::Lorem.word"
|
17
|
+
}
|
18
|
+
|
19
|
+
def initialize(nodes, options = {}, app_name = "app")
|
20
|
+
# thor related
|
21
|
+
@options = options
|
22
|
+
@destination_stack = [self.class.source_root]
|
23
|
+
|
24
|
+
@resources = nodes.keys.select {|n| n if n.pluralize == n}
|
25
|
+
resources.each do |resource|
|
26
|
+
@resource_name = resource.camelize
|
27
|
+
@fields = @resource_name.singularize.
|
28
|
+
constantize.fields.inject({}) do |memo, field|
|
29
|
+
|
30
|
+
field_value =
|
31
|
+
if PROTOBUF_FIXTURES[field.type_class]
|
32
|
+
# TODO this is too general need some way to signal
|
33
|
+
# primary key which is addressable
|
34
|
+
if field.name.to_s.include?("id")
|
35
|
+
"id = #{PROTOBUF_FIXTURES[field.type_class]}"
|
36
|
+
else
|
37
|
+
PROTOBUF_FIXTURES[field.type_class]
|
38
|
+
end
|
39
|
+
elsif field.type_class.to_s.demodulize == "Controls"
|
40
|
+
"#{field.type_class}.new(minimal_controls_for(item_id))"
|
41
|
+
else
|
42
|
+
# TODO recursively gen these relations
|
43
|
+
"[#{field.type_class}.new(params = {})]"
|
44
|
+
end
|
45
|
+
|
46
|
+
memo[field.name] = field_value
|
47
|
+
memo
|
48
|
+
end
|
49
|
+
template(
|
50
|
+
'templates/type.rb',
|
51
|
+
File.join("../../generated/#{app_name}/resources/#{resource}.rb")
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/picatrix/routes.rb
CHANGED
@@ -2,39 +2,68 @@ module Picatrix
|
|
2
2
|
# this class is the data structure you'll construct the Sinatra
|
3
3
|
# routes and their corresponding handling method bodies
|
4
4
|
class Routes
|
5
|
-
attr_accessor :unique_routes, :root_path
|
5
|
+
attr_accessor :unique_routes, :root_path, :controls
|
6
6
|
|
7
7
|
def initialize(edges, control_templates)
|
8
|
+
@controls = {}
|
9
|
+
|
10
|
+
edges.collect do |edge|
|
11
|
+
edge.values.first[:target]
|
12
|
+
end.each do |resource|
|
13
|
+
if (control = control_templates[resource])
|
14
|
+
@controls.merge!(control.to_hash)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
@unique_routes = edges.collect do |edge|
|
9
19
|
source = edge.keys.first
|
10
20
|
target = edge.values.first[:target]
|
11
21
|
properties = edge[source]
|
12
22
|
|
13
|
-
link_relation = properties[:link_relation]
|
23
|
+
link_relation = properties[:link_relation]
|
14
24
|
|
15
25
|
if source == "root"
|
16
26
|
#TODO right now app is using this in place of
|
17
27
|
# figuring out parent collections of collections
|
18
28
|
@root_path = target
|
29
|
+
source = target
|
19
30
|
end
|
20
31
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
32
|
+
path = case properties[:method]
|
33
|
+
when :post
|
34
|
+
source.pluralize
|
35
|
+
when :get
|
36
|
+
if is_collection?(target)
|
37
|
+
"#{source.pluralize}"
|
38
|
+
else
|
39
|
+
"#{source.pluralize}/#{link_relation}"
|
40
|
+
end
|
41
|
+
when :patch
|
42
|
+
"#{source.pluralize}/:item_id"
|
43
|
+
when :put
|
44
|
+
"#{source.pluralize}/:item_id"
|
45
|
+
when :delete
|
46
|
+
"#{source.pluralize}/:item_id"
|
47
|
+
else
|
48
|
+
"#{source.pluralize}/#{link_relation}"
|
49
|
+
end
|
25
50
|
|
26
51
|
{
|
27
|
-
path:
|
52
|
+
path: path,
|
53
|
+
target: target,
|
28
54
|
link_relation: link_relation,
|
29
55
|
method: properties[:method],
|
30
|
-
|
56
|
+
response_resource: {
|
31
57
|
name: target.camelcase.constantize,
|
32
|
-
collection: (target
|
33
|
-
}
|
34
|
-
control_templates: templates
|
58
|
+
collection: is_collection?(target)
|
59
|
+
}
|
35
60
|
}
|
36
61
|
# make sure routes are not duplicated
|
37
62
|
end.compact.uniq {|e| [e[:path], e[:method]]}
|
38
63
|
end
|
64
|
+
|
65
|
+
def is_collection?(target)
|
66
|
+
(target == target.pluralize)
|
67
|
+
end
|
39
68
|
end
|
40
69
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
package mason;
|
1
|
+
package picatrix.mason;
|
2
2
|
|
3
|
+
// TODO this doesnt really make sense as enum, should deprecate
|
3
4
|
enum HTTPVerb {
|
4
5
|
GET = 1;
|
5
6
|
PUT = 2;
|
@@ -18,37 +19,39 @@ message Up {
|
|
18
19
|
optional string title = 2;
|
19
20
|
}
|
20
21
|
|
21
|
-
// Filter should be broken out into custom Picatrix stuff
|
22
|
-
// as it's not really part of the Mason spec
|
23
22
|
message Filter {
|
24
|
-
enum HrefTemplate {
|
25
|
-
TRUE = 0;
|
26
|
-
FALSE = 1;
|
27
|
-
}
|
28
|
-
|
29
23
|
optional string href = 1;
|
30
|
-
|
31
|
-
|
24
|
+
required HTTPVerb method = 2 [default = GET];
|
25
|
+
required bool isHrefTemplate = 3 [default = true];
|
26
|
+
optional string title = 4;
|
27
|
+
|
28
|
+
extensions 10 to 19;
|
32
29
|
}
|
33
30
|
|
34
31
|
message Create {
|
35
32
|
optional string href = 1;
|
36
|
-
|
33
|
+
required HTTPVerb method = 2 [default = POST];
|
37
34
|
optional string title = 3;
|
38
35
|
optional string encoding = 4;
|
36
|
+
|
37
|
+
extensions 10 to 19;
|
39
38
|
}
|
40
39
|
|
41
40
|
message Edit {
|
42
41
|
optional string href = 1;
|
43
|
-
|
42
|
+
required HTTPVerb method = 2 [default = PATCH];
|
44
43
|
optional string title = 3;
|
45
44
|
optional string encoding = 4;
|
45
|
+
|
46
|
+
extensions 10 to 19;
|
46
47
|
}
|
47
48
|
|
48
49
|
message Remove {
|
49
50
|
optional string href = 1;
|
50
|
-
|
51
|
+
required HTTPVerb method = 2 [default = DELETE];
|
51
52
|
optional string title = 3;
|
53
|
+
|
54
|
+
extensions 10 to 19;
|
52
55
|
}
|
53
56
|
|
54
57
|
// when viewing an item directly, you see
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import "picatrix.mason.proto";
|
2
|
+
|
3
|
+
// representation objects
|
4
|
+
<% @representation_klasses.each do |klass| %>
|
5
|
+
message <%= klass %> {
|
6
|
+
optional int32 id = 1;
|
7
|
+
|
8
|
+
message Controls {
|
9
|
+
optional picatrix.mason.Self self = 1;
|
10
|
+
}
|
11
|
+
|
12
|
+
optional Controls controls = 2;
|
13
|
+
<% if klass.pluralize == klass %>
|
14
|
+
repeated <%= klass.singularize.camelcase %> collection = 3;
|
15
|
+
<% end %>
|
16
|
+
}
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
// generic CRUFD controls to extend
|
20
|
+
<% @item_representations.each do |item| %>// per item representation
|
21
|
+
<% ITEM_ACTIONS.each do |action| %>
|
22
|
+
message <%= item %><%= action %>Template {
|
23
|
+
}
|
24
|
+
extend picatrix.mason.<%= action %> {
|
25
|
+
optional <%= item %><%= action %>Template <%= action.underscore %>_template = 10;
|
26
|
+
}
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
29
|
+
<% @col_representations.each do |item| %>// per collection representation
|
30
|
+
<% COL_ACTIONS.each do |action| %>
|
31
|
+
message <%= item %><%= action %>Template {
|
32
|
+
}
|
33
|
+
extend picatrix.mason.<%= action %> {
|
34
|
+
optional <%= item %><%= action %>Template <%= action.underscore %>_template = 10;
|
35
|
+
}
|
36
|
+
<% end %>
|
37
|
+
<% end %>
|
38
|
+
// totally custom controls
|
39
|
+
<% @link_relations.each do |link_relation| %>
|
40
|
+
message <%= link_relation %> {
|
41
|
+
}
|
42
|
+
<% end %>
|
@@ -3,9 +3,18 @@ require 'sinatra/json'
|
|
3
3
|
require 'protobuf'
|
4
4
|
require 'require_all'
|
5
5
|
|
6
|
-
|
6
|
+
$:.unshift(File.join(File.dirname(__FILE__)))
|
7
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'support'))
|
8
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'resources'))
|
9
|
+
|
10
|
+
require("./generated/<%= @app_name %>/support/picatrix.mason.pb")
|
11
|
+
require("./generated/<%= @app_name %>/support/mason")
|
12
|
+
require("./generated/<%= @app_name %>/support/<%= @app_name %>.pb")
|
13
|
+
require_all("generated/<%= @app_name %>/resources/*.rb")
|
7
14
|
|
8
15
|
class <%= @app_name.camelize %> < Sinatra::Base
|
16
|
+
include Picatrix::Mason
|
17
|
+
|
9
18
|
set :public_folder => "public", :static => true
|
10
19
|
|
11
20
|
get '/rels' do
|
@@ -23,142 +32,21 @@ class <%= @app_name.camelize %> < Sinatra::Base
|
|
23
32
|
end
|
24
33
|
|
25
34
|
<% @routes.unique_routes.each do |route| %>
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
<% if route[:control_templates][route[:path]][route[:method]] %>
|
33
|
-
controls = send(:<%= route[:control_templates][route[:path]][route[:method]] %>, <%= route[:resource][:name] %>).call("<%= route[:path] %>", params)
|
34
|
-
<% else %>
|
35
|
-
controls = {}
|
36
|
-
<% end %>
|
37
|
-
|
38
|
-
json(
|
39
|
-
{:@controls => controls}.
|
40
|
-
merge!({"<%= route[:resource][:name].to_s.underscore %>" => items})
|
41
|
-
)
|
42
|
-
end
|
43
|
-
<% else %>
|
44
|
-
<%= route[:method] %> "/<%= route[:path] %>/:item_id" do
|
45
|
-
item = <%= route[:resource][:name].to_s.pluralize.constantize %>Resource.<%= send_method_for(route[:method]).to_s %>(params[:item_id])
|
46
|
-
|
47
|
-
<% if route[:control_templates][route[:path]][route[:method]] %>
|
48
|
-
controls = send(:<%= route[:control_templates][route[:path]][route[:method]] %>, <%= route[:resource][:name] %>).call("<%= route[:path] %>", params)
|
49
|
-
<% else %>
|
35
|
+
<%= route[:method] %> "/<%= route[:path] %>" do
|
36
|
+
<% if route[:response_resource][:collection] %>
|
37
|
+
response_body = {"<%= route[:response_resource][:name].to_s.underscore %>" => <%= route[:response_resource][:name] %>Resource.as_hashes}
|
38
|
+
<% else %>
|
39
|
+
response_body = <%= route[:response_resource][:name].to_s.pluralize.constantize %>Resource.<%= send_method_for(route[:method]).to_s %>(params[:item_id]).to_hash.except(:controls)
|
40
|
+
<% end %>
|
50
41
|
controls = {}
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
42
|
+
<% if @routes.controls[route[:target]][route[:method]] %>
|
43
|
+
<% @routes.controls[route[:target]][route[:method]].each do |control| %>
|
44
|
+
controls.merge!(send("<%= control %>".to_sym, <%= route[:response_resource][:name] %>).call("<%= route[:target] %>".pluralize, params))
|
45
|
+
<% end %>
|
46
|
+
<% end %>
|
47
|
+
json({
|
48
|
+
:@controls => controls
|
49
|
+
}.merge!(response_body))
|
57
50
|
end
|
58
|
-
<% end %>
|
59
51
|
<% end %>
|
60
|
-
|
61
|
-
def mason_up(type)
|
62
|
-
if type == collection
|
63
|
-
proc do |path, params|
|
64
|
-
{
|
65
|
-
up: Mason::Up.new(
|
66
|
-
{
|
67
|
-
href: "http://localhost:9292/#{<%= @root_path %>}"
|
68
|
-
}
|
69
|
-
).to_hash
|
70
|
-
}
|
71
|
-
end
|
72
|
-
else
|
73
|
-
proc do |path, params|
|
74
|
-
{
|
75
|
-
up: Mason::Up.new(
|
76
|
-
{
|
77
|
-
href: "http://localhost:9292/#{path}"
|
78
|
-
}
|
79
|
-
).to_hash
|
80
|
-
}
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
def mason_self(item)
|
85
|
-
proc do |path, params|
|
86
|
-
if params[:item_id]
|
87
|
-
{
|
88
|
-
self: Mason::Self.new(
|
89
|
-
{
|
90
|
-
href: "http://localhost:9292/#{path}/#{params[:item_id]}"
|
91
|
-
}
|
92
|
-
).to_hash
|
93
|
-
}
|
94
|
-
else
|
95
|
-
{
|
96
|
-
self: Mason::Self.new(
|
97
|
-
{
|
98
|
-
href: "http://localhost:9292/#{path}"
|
99
|
-
}
|
100
|
-
).to_hash
|
101
|
-
}
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
def edit(item)
|
106
|
-
proc do |path, params|
|
107
|
-
{
|
108
|
-
edit: ("Edit" + item.to_s.singularize.camelize).constantize.new(
|
109
|
-
{
|
110
|
-
form: Mason::Edit.new(
|
111
|
-
href: "http://localhost:9292/#{path}/#{params[:item_id]}"
|
112
|
-
)
|
113
|
-
}
|
114
|
-
).to_hash
|
115
|
-
}
|
116
|
-
end
|
117
|
-
end
|
118
|
-
def remove(item)
|
119
|
-
proc do |path, params|
|
120
|
-
{
|
121
|
-
remove: Mason::Remove.new(
|
122
|
-
{
|
123
|
-
href: "http://localhost:9292/#{path}/#{params[:item_id]}"
|
124
|
-
}
|
125
|
-
).to_hash
|
126
|
-
}
|
127
|
-
end
|
128
|
-
end
|
129
|
-
def create(item)
|
130
|
-
proc do |path, params|
|
131
|
-
{
|
132
|
-
create: ("Create" + item.to_s.singularize.camelize).constantize.new(
|
133
|
-
{
|
134
|
-
form: Mason::Create.new(
|
135
|
-
href: ""
|
136
|
-
)
|
137
|
-
}
|
138
|
-
)
|
139
|
-
}
|
140
|
-
end
|
141
|
-
end
|
142
|
-
def filter(collection)
|
143
|
-
# TODO this prob cant really work here
|
144
|
-
proc do |path, params|
|
145
|
-
if params[:item_id]
|
146
|
-
{
|
147
|
-
"<%= @prefix %>filter": Mason::Filter.new(
|
148
|
-
{
|
149
|
-
href: "http://localhost:9292/#{path}/#{params[:item_id]}"
|
150
|
-
}
|
151
|
-
).to_hash
|
152
|
-
}
|
153
|
-
else
|
154
|
-
{
|
155
|
-
"<%= @prefix %>filter": Mason::Filter.new(
|
156
|
-
{
|
157
|
-
href: "http://localhost:9292/#{path}"
|
158
|
-
}
|
159
|
-
).to_hash
|
160
|
-
}
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
52
|
end
|