picatrix 0.5.0 → 0.5.5

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 (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