picatrix 0.5.0 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +17 -37
  4. data/Rakefile +35 -0
  5. data/bin/console +8 -4
  6. data/bin/post-receive.sh +3 -0
  7. data/bin/setup +7 -1
  8. data/generated/zoo.dot +37 -0
  9. data/generated/zoo.dot.png +0 -0
  10. data/{lib/picatrix/generated/view_submissions.rb → generated/zoo/resources/animals.rb} +10 -13
  11. data/generated/zoo/support/mason.rb +114 -0
  12. data/generated/zoo/support/namespaces.json +7 -0
  13. data/generated/zoo/support/picatrix.mason.pb.rb +95 -0
  14. data/generated/zoo/support/zoo.pb.rb +76 -0
  15. data/generated/zoo/support/zoo.proto +65 -0
  16. data/generated/zoo/zoo.rb +116 -0
  17. data/lib/picatrix.rb +25 -10
  18. data/lib/picatrix/buffy.rb +38 -40
  19. data/lib/picatrix/cruddy.rb +18 -16
  20. data/lib/picatrix/jenny.rb +31 -5
  21. data/lib/picatrix/link_relation_index.rb +1 -1
  22. data/lib/picatrix/pacman.rb +12 -6
  23. data/lib/picatrix/protoc.rb +7 -0
  24. data/lib/picatrix/prototyper.rb +56 -0
  25. data/lib/picatrix/routes.rb +40 -11
  26. data/{definitions/mason.proto → lib/picatrix/support/picatrix.mason.proto} +16 -13
  27. data/lib/picatrix/templates/app.proto +42 -0
  28. data/lib/picatrix/templates/app.rb +24 -136
  29. data/lib/picatrix/templates/mason.rb +114 -0
  30. data/lib/picatrix/templates/namespaces.json +7 -0
  31. data/lib/picatrix/templates/type.rb +6 -3
  32. data/lib/picatrix/transform_to_hash.rb +2 -2
  33. data/lib/picatrix/version.rb +1 -1
  34. data/notes.md +34 -0
  35. metadata +20 -10
  36. data/definitions/app.proto +0 -47
  37. data/lib/picatrix/fixtures/view_submission.rb +0 -19
  38. data/lib/picatrix/generated/app.pb.rb +0 -79
  39. data/lib/picatrix/generated/app.rb +0 -209
  40. data/lib/picatrix/generated/mason.pb.rb +0 -90
  41. data/lib/picatrix/generated/mason/namespaces.json +0 -7
@@ -26,30 +26,56 @@ module Picatrix
26
26
  @meta = {}
27
27
 
28
28
  if File.exist?(
29
- mason_path = "lib/picatrix/generated/mason/namespaces.json"
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
- mason_path
33
+ namespaces_path
34
34
  )
35
35
  )
36
36
  @prefix = "#{@namespaces["@namespaces"].keys.first}:"
37
37
  else
38
- puts "no generated/mason/namespaces.json file"
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 just_do_it
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].delete('"')
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]}"
@@ -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
- @hash = TransformToHash.new.apply(
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
- grouped = @hash.group_by {|e| e.keys.first}
18
- @nodes = grouped[:node].flat_map {|e| e[:node]}.reduce({}, :merge)
19
- @edges = grouped[:edge].flat_map {|e| e[:edge]}
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,7 @@
1
+ module Picatrix
2
+ module Protoc
3
+ def self.to_ruby(app_name)
4
+ `protoc -I ./lib/picatrix/support -I ./generated/#{app_name}/support --ruby_out ./generated/#{app_name}/support generated/#{app_name}/support/#{app_name}.proto`
5
+ end
6
+ end
7
+ 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
@@ -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].delete('"')
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
- templates = {}
22
- if (control = control_templates[target])
23
- templates.merge!(control.to_hash)
24
- end
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: target,
52
+ path: path,
53
+ target: target,
28
54
  link_relation: link_relation,
29
55
  method: properties[:method],
30
- resource: {
56
+ response_resource: {
31
57
  name: target.camelcase.constantize,
32
- collection: (target == target.pluralize)
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
- optional HTTPVerb method = 2 [default = GET];
31
- optional HrefTemplate isHrefTemplate = 3 [default = TRUE];
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
- optional HTTPVerb method = 2 [default = POST];
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
- optional HTTPVerb method = 2 [default = PATCH];
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
- optional HTTPVerb method = 2 [default = DELETE];
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
- require_all("./lib/picatrix/generated/*.rb")
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
- <% if route[:resource][:collection] %>
27
- <%= route[:method] %> "/<%= route[:path] %>" do
28
- items = <%= route[:resource][:name] %>Resource.all.collection.collect do |r|
29
- r.to_hash
30
- end
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
- <% end %>
52
-
53
- json(
54
- {:@controls => controls}.
55
- merge!(item.to_hash.except(:controls))
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