picatrix 0.2.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +12 -2
- data/README.md +46 -30
- data/Rakefile +1 -0
- data/bin/console +7 -1
- data/definitions/app.proto +47 -0
- data/definitions/mason.proto +63 -0
- data/lib/picatrix/buffy.rb +56 -0
- data/lib/picatrix/cruddy.rb +45 -0
- data/lib/picatrix/digraph_parser.rb +7 -0
- data/lib/picatrix/fixtures/view_submission.rb +19 -0
- data/lib/picatrix/generated/app.pb.rb +79 -0
- data/lib/picatrix/generated/app.rb +171 -60
- data/lib/picatrix/generated/mason/namespaces.json +7 -0
- data/lib/picatrix/generated/mason.pb.rb +90 -0
- data/lib/picatrix/generated/view_submissions.rb +49 -0
- data/lib/picatrix/jenny.rb +30 -155
- data/lib/picatrix/link_relation_index.rb +36 -0
- data/lib/picatrix/pacman.rb +8 -5
- data/lib/picatrix/routes.rb +40 -0
- data/lib/picatrix/templates/app.rb +138 -22
- data/lib/picatrix/templates/control.rb +15 -0
- data/lib/picatrix/templates/type.rb +35 -21
- data/lib/picatrix/transform_to_hash.rb +0 -10
- data/lib/picatrix/version.rb +1 -1
- data/lib/picatrix.rb +22 -1
- data/picatrix.gemspec +2 -0
- metadata +42 -9
- data/lib/picatrix/generated/view_submission.rb +0 -27
- data/lib/picatrix/schemas/view_submission_item.json +0 -12
- data/lib/picatrix/templates/mason/submission_filter_template.json +0 -4
- data/lib/picatrix/templates/mason/view_submission.json +0 -35
- data/lib/picatrix/templates/mason/view_submission_min.json +0 -11
- data/lib/picatrix/templates/mason/view_submissions.json +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28283e340d828d5a22f3152ccd81c502b863eccf
|
4
|
+
data.tar.gz: d5c7a1a4234333f7002f3a6016c325a1fcd6f636
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c71a9ae39ddf75673c92affe67f2a93e5e61e81aef091596de7a037109ee301cf731439c67b4659cc8393d6de8b61f6910ddde9ba3639f11fbdfe4c36849e9c4
|
7
|
+
data.tar.gz: 4f113d91d4e052728a87cdc347a902c732b2e3df02a33b150d2524fcb183b6a5caa1d6bd2e8d75afd97625a7a45d4594239389fa99822c8e80872195cf686b1d
|
data/Gemfile.lock
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
picatrix (0.
|
4
|
+
picatrix (0.2.0)
|
5
5
|
activesupport
|
6
|
+
faker
|
6
7
|
grape
|
7
8
|
grape-roar
|
8
9
|
parslet
|
10
|
+
protobuf
|
9
11
|
require_all
|
10
12
|
roar
|
11
13
|
ruby-graphviz
|
@@ -16,7 +18,7 @@ PATH
|
|
16
18
|
GEM
|
17
19
|
remote: https://rubygems.org/
|
18
20
|
specs:
|
19
|
-
activesupport (4.2.
|
21
|
+
activesupport (4.2.5)
|
20
22
|
i18n (~> 0.7)
|
21
23
|
json (~> 1.7, >= 1.7.7)
|
22
24
|
minitest (~> 5.1)
|
@@ -36,6 +38,8 @@ GEM
|
|
36
38
|
thread_safe (~> 0.3, >= 0.3.1)
|
37
39
|
diff-lcs (1.2.5)
|
38
40
|
equalizer (0.0.11)
|
41
|
+
faker (1.5.0)
|
42
|
+
i18n (~> 0.5)
|
39
43
|
grape (0.13.0)
|
40
44
|
activesupport
|
41
45
|
builder
|
@@ -54,11 +58,17 @@ GEM
|
|
54
58
|
ice_nine (0.11.1)
|
55
59
|
json (1.8.3)
|
56
60
|
method_source (0.8.2)
|
61
|
+
middleware (0.1.0)
|
57
62
|
minitest (5.8.2)
|
58
63
|
multi_json (1.11.2)
|
59
64
|
multi_xml (0.5.5)
|
60
65
|
parslet (1.7.1)
|
61
66
|
blankslate (>= 2.0, <= 4.0)
|
67
|
+
protobuf (3.5.5)
|
68
|
+
activesupport (>= 3.2)
|
69
|
+
middleware
|
70
|
+
thor
|
71
|
+
thread_safe
|
62
72
|
pry (0.10.3)
|
63
73
|
coderay (~> 1.1.0)
|
64
74
|
method_source (~> 0.8.1)
|
data/README.md
CHANGED
@@ -5,27 +5,23 @@
|
|
5
5
|
'. (___.'_/
|
6
6
|
((-((-''mrf
|
7
7
|
|
8
|
-
#
|
8
|
+
# picatrix
|
9
9
|
|
10
|
-
An opinionated hypermedia API generator.
|
10
|
+
An opinionated hypermedia API generator. :grin:
|
11
11
|
|
12
|
-
Requires:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
- collection nodes should be given `shape=folder` and pluralized
|
17
|
-
- the item node belonging to the collection node should be its singularized version and `shape=rectangle`
|
18
|
-
- forms (often called templates in the code, because they are a template for a valid request to the server) are given a `_template` suffix and `shape=note`
|
19
|
-
- `style=dotted` means the related object will be represented inline (embedded)
|
20
|
-
- `style=bold` and `arrowhead=normal` means `POST`
|
21
|
-
- `arrowhead=normal` alone means `PATCH`
|
22
|
-
- `color=red fontcolor=red` means `DELETE`
|
12
|
+
## Requires:
|
13
|
+
|
14
|
+
- valid `.dot` file with [additional style guidelines](#dot-file-styles)
|
15
|
+
- Protobuf [definitions of inputs/outputs](#protobuf-definitions-of-representations) for all state transitions, by node
|
23
16
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
-
|
17
|
+
|
18
|
+
## Outputs:
|
19
|
+
|
20
|
+
- basic Sinatra app and mock objects fitting your schema
|
21
|
+
- protobuf generated representation objects (you dont edit these)
|
22
|
+
- fixture interface to map to your persistance layer, is generated with fake data generators (you do edit these)
|
23
|
+
- `application/vnd.mason+json`
|
24
|
+
- controls
|
29
25
|
- state diagram image ([see below](https://github.com/mooreniemi/picatrix#sample-image-output))
|
30
26
|
|
31
27
|
## About
|
@@ -36,6 +32,12 @@ The ideal way to use Picatrix is to do an exercise in designing your API "outsid
|
|
36
32
|
|
37
33
|
## Installation
|
38
34
|
|
35
|
+
Install system dependencies (Mac OS sample):
|
36
|
+
|
37
|
+
```bash
|
38
|
+
$ brew install graphviz
|
39
|
+
$ brew install protobuf
|
40
|
+
```
|
39
41
|
Add this line to your application's Gemfile:
|
40
42
|
|
41
43
|
```ruby
|
@@ -50,6 +52,25 @@ Or install it yourself as:
|
|
50
52
|
|
51
53
|
$ gem install picatrix
|
52
54
|
|
55
|
+
## getting ready
|
56
|
+
|
57
|
+
### .dot file styles
|
58
|
+
|
59
|
+
- edge style defaults to `arrowhead=odiamond` to signify default requests are `GET`
|
60
|
+
- the first node listed should be the root, and is signified by `xlabel=` and `shape=point`, you should never need to change this (unless you are knitting together workflows - future thing)
|
61
|
+
- collection nodes should be given `shape=folder` and pluralized
|
62
|
+
- the item node belonging to the collection node should be its singularized version and `shape=rectangle`
|
63
|
+
- `style=bold` and `arrowhead=normal` means `POST`
|
64
|
+
- `arrowhead=normal` alone means `PATCH`
|
65
|
+
- `color=red fontcolor=red` means `DELETE`
|
66
|
+
|
67
|
+
### protobuf definitions of representations
|
68
|
+
|
69
|
+
- must match the node names (each node signifies a representation)
|
70
|
+
- consider
|
71
|
+
- from the perspective of the client at any request, what does it need to make the request and have the server fufill its contract?
|
72
|
+
- from the perspective of the server, what might the client want in response for it to make any interesting subsequent requests from this node in the workflow?
|
73
|
+
- inputs will map onto controls, and outputs will map on to representations (dereferenced content)
|
53
74
|
## Usage
|
54
75
|
|
55
76
|
In its final state, the intent is to use it as a CLI:
|
@@ -70,8 +91,6 @@ $ bin/picapica dg_api.dot app_name
|
|
70
91
|
|
71
92
|
Nodes and edges translate roughly into application and/or resource states, and state transitions.
|
72
93
|
|
73
|
-
Dotted lines represent inlined relations. So, `View submissions` inlines `View submission` by `:item_id` (in this example a number).
|
74
|
-
|
75
94
|
Labels on edges are link relations. Confusingly the actual _URL_ of the state transition (moving to the next state) is still the target node (essentially, think of the state transition name as the same as the name of the ideal output) _and_ its link relation to its source. So, `A (make_B_do_x)-> B` is `B/make_B_do_x`. This is still (obviously) sketchy.
|
76
95
|
|
77
96
|
## Development
|
@@ -81,16 +100,13 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
81
100
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
82
101
|
|
83
102
|
### TODO
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
#### done
|
93
|
-
- parse dot file in Ruby
|
103
|
+
|
104
|
+
process goes: .dot file + `prep` (1)-> proto file from nodes + user input into .proto + `proto` (2)-> ruby representation classes + user input to fixturize (3)-> `g` (4)-> `rackup lib/picatrix/generated/app.rb`
|
105
|
+
|
106
|
+
1. outside in design process produced a diagram, which is written into a .dot file
|
107
|
+
2. all nodes are representations of resources, and thus get proto objects generated for them, here is where the inputs/outputs are formed into a schema
|
108
|
+
3. representation nodes now have ruby classes that reflect the information that will be sent back and forth in messages so we generate the app
|
109
|
+
4. boot up the app
|
94
110
|
|
95
111
|
## Contributing
|
96
112
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -3,5 +3,11 @@
|
|
3
3
|
require "bundler/setup"
|
4
4
|
require "picatrix"
|
5
5
|
|
6
|
+
# generate ruby classes
|
7
|
+
`protoc -I ./definitions --ruby_out ./lib/picatrix/generated definitions/*.proto`
|
8
|
+
|
9
|
+
require './lib/picatrix/generated/mason.pb.rb'
|
10
|
+
require './lib/picatrix/generated/app.pb.rb'
|
11
|
+
|
6
12
|
require "pry"
|
7
|
-
|
13
|
+
Pry.start
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import "mason.proto";
|
2
|
+
|
3
|
+
// objects
|
4
|
+
message ViewSubmission {
|
5
|
+
optional int32 id = 1;
|
6
|
+
optional string category = 2;
|
7
|
+
optional string photo = 3;
|
8
|
+
|
9
|
+
// items probably want a minimal representation
|
10
|
+
// of available controls when in collection view
|
11
|
+
message Controls {
|
12
|
+
optional mason.Self self = 2;
|
13
|
+
}
|
14
|
+
|
15
|
+
message Attachment {
|
16
|
+
optional int32 id = 1;
|
17
|
+
}
|
18
|
+
|
19
|
+
optional Controls controls = 4;
|
20
|
+
repeated Attachment attachments = 5;
|
21
|
+
}
|
22
|
+
|
23
|
+
message ViewSubmissions {
|
24
|
+
repeated ViewSubmission collection = 1;
|
25
|
+
}
|
26
|
+
|
27
|
+
// controls
|
28
|
+
message EditViewSubmission {
|
29
|
+
// someday gen this from schema?
|
30
|
+
optional mason.Edit form = 3;
|
31
|
+
message Template {
|
32
|
+
optional string category = 1;
|
33
|
+
optional string has_photo = 2;
|
34
|
+
}
|
35
|
+
optional Template template = 4;
|
36
|
+
}
|
37
|
+
|
38
|
+
message CreateViewSubmission {
|
39
|
+
// someday gen this from schema?
|
40
|
+
optional mason.Create form = 3;
|
41
|
+
message Template {
|
42
|
+
optional string category = 1;
|
43
|
+
optional string has_photo = 2;
|
44
|
+
}
|
45
|
+
optional Template template = 4;
|
46
|
+
}
|
47
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
package mason;
|
2
|
+
|
3
|
+
enum HTTPVerb {
|
4
|
+
GET = 1;
|
5
|
+
PUT = 2;
|
6
|
+
PATCH = 3;
|
7
|
+
DELETE = 4;
|
8
|
+
POST = 5;
|
9
|
+
}
|
10
|
+
|
11
|
+
message Self {
|
12
|
+
optional string href = 1;
|
13
|
+
optional string title = 2;
|
14
|
+
}
|
15
|
+
|
16
|
+
message Up {
|
17
|
+
optional string href = 1;
|
18
|
+
optional string title = 2;
|
19
|
+
}
|
20
|
+
|
21
|
+
// Filter should be broken out into custom Picatrix stuff
|
22
|
+
// as it's not really part of the Mason spec
|
23
|
+
message Filter {
|
24
|
+
enum HrefTemplate {
|
25
|
+
TRUE = 0;
|
26
|
+
FALSE = 1;
|
27
|
+
}
|
28
|
+
|
29
|
+
optional string href = 1;
|
30
|
+
optional HTTPVerb method = 2 [default = GET];
|
31
|
+
optional HrefTemplate isHrefTemplate = 3 [default = TRUE];
|
32
|
+
}
|
33
|
+
|
34
|
+
message Create {
|
35
|
+
optional string href = 1;
|
36
|
+
optional HTTPVerb method = 2 [default = POST];
|
37
|
+
optional string title = 3;
|
38
|
+
optional string encoding = 4;
|
39
|
+
}
|
40
|
+
|
41
|
+
message Edit {
|
42
|
+
optional string href = 1;
|
43
|
+
optional HTTPVerb method = 2 [default = PATCH];
|
44
|
+
optional string title = 3;
|
45
|
+
optional string encoding = 4;
|
46
|
+
}
|
47
|
+
|
48
|
+
message Remove {
|
49
|
+
optional string href = 1;
|
50
|
+
optional HTTPVerb method = 2 [default = DELETE];
|
51
|
+
optional string title = 3;
|
52
|
+
}
|
53
|
+
|
54
|
+
// when viewing an item directly, you see
|
55
|
+
// all available controls for it
|
56
|
+
message Controls {
|
57
|
+
optional Self self = 2;
|
58
|
+
optional Up up = 3;
|
59
|
+
optional Edit edit = 4;
|
60
|
+
optional Remove remove = 5;
|
61
|
+
optional Create create = 6;
|
62
|
+
optional Filter filter = 7;
|
63
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'protobuf'
|
2
|
+
require 'faker'
|
3
|
+
|
4
|
+
module Picatrix
|
5
|
+
class Buffy
|
6
|
+
include Thor::Base
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
attr_accessor :destination_stack, :options,
|
10
|
+
:resources, :id
|
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 = {})
|
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/#{resource}.rb")
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Picatrix
|
2
|
+
class Cruddy
|
3
|
+
attr_accessor :controls
|
4
|
+
|
5
|
+
def initialize(edges)
|
6
|
+
@controls = edges.inject({}) do |memo, edge|
|
7
|
+
source = edge.keys.first
|
8
|
+
target = edge[source][:target]
|
9
|
+
|
10
|
+
next memo if source.upcase == "ROOT"
|
11
|
+
|
12
|
+
memo[target] = appropriate_controls_for(target)
|
13
|
+
memo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def appropriate_controls_for(source)
|
19
|
+
#TODO unless these become more diff again, should combine
|
20
|
+
if source.pluralize == source # collection?
|
21
|
+
{
|
22
|
+
source => template_map(:collection)
|
23
|
+
}
|
24
|
+
else
|
25
|
+
{
|
26
|
+
source => template_map(:item)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def template_map(type)
|
31
|
+
{
|
32
|
+
item: {
|
33
|
+
get: :mason_self,
|
34
|
+
patch: :edit,
|
35
|
+
put: :edit,
|
36
|
+
delete: :remove
|
37
|
+
},
|
38
|
+
collection: {
|
39
|
+
post: :create,
|
40
|
+
get: :filter
|
41
|
+
}
|
42
|
+
}[type]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -10,7 +10,14 @@ module Picatrix
|
|
10
10
|
(newline.absent? >> any).repeat(1) >>
|
11
11
|
newline
|
12
12
|
end
|
13
|
+
rule(:graph_setting) do
|
14
|
+
indent >>
|
15
|
+
(str(";").absent? >> any).repeat(0) >>
|
16
|
+
str(";") >>
|
17
|
+
newline
|
18
|
+
end
|
13
19
|
rule(:default_definitions) do
|
20
|
+
graph_setting.repeat(0) >>
|
14
21
|
indent >>
|
15
22
|
str("edge [") >>
|
16
23
|
(str("]").absent? >> any).repeat(0) >>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class ViewSubmissionFixture
|
2
|
+
# real object has more properties
|
3
|
+
def self.attributes
|
4
|
+
{
|
5
|
+
category: random_category,
|
6
|
+
photo: random_photo,
|
7
|
+
secret_data: "that won't be shared",
|
8
|
+
pci_data: "really won't appear"
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.random_category
|
13
|
+
%w{ cats }.sample
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.random_photo
|
17
|
+
%w{ http://imgur.com/r/cats/myEqIZx }.sample
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
##
|
4
|
+
# This file is auto-generated. DO NOT EDIT!
|
5
|
+
#
|
6
|
+
require 'protobuf/message'
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
# Imports
|
11
|
+
#
|
12
|
+
require_relative 'mason.pb.rb'
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
# Message Classes
|
17
|
+
#
|
18
|
+
class ViewSubmission < ::Protobuf::Message
|
19
|
+
class Controls < ::Protobuf::Message; end
|
20
|
+
class Attachment < ::Protobuf::Message; end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class ViewSubmissions < ::Protobuf::Message; end
|
25
|
+
class EditViewSubmission < ::Protobuf::Message
|
26
|
+
class Template < ::Protobuf::Message; end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class CreateViewSubmission < ::Protobuf::Message
|
31
|
+
class Template < ::Protobuf::Message; end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
# Message Fields
|
39
|
+
#
|
40
|
+
class ViewSubmission
|
41
|
+
class Controls
|
42
|
+
optional ::Mason::Self, :self, 2
|
43
|
+
end
|
44
|
+
|
45
|
+
class Attachment
|
46
|
+
optional :int32, :id, 1
|
47
|
+
end
|
48
|
+
|
49
|
+
optional :int32, :id, 1
|
50
|
+
optional :string, :category, 2
|
51
|
+
optional :string, :photo, 3
|
52
|
+
optional ::ViewSubmission::Controls, :controls, 4
|
53
|
+
repeated ::ViewSubmission::Attachment, :attachments, 5
|
54
|
+
end
|
55
|
+
|
56
|
+
class ViewSubmissions
|
57
|
+
repeated ::ViewSubmission, :collection, 1
|
58
|
+
end
|
59
|
+
|
60
|
+
class EditViewSubmission
|
61
|
+
class Template
|
62
|
+
optional :string, :category, 1
|
63
|
+
optional :string, :has_photo, 2
|
64
|
+
end
|
65
|
+
|
66
|
+
optional ::Mason::Edit, :form, 3
|
67
|
+
optional ::EditViewSubmission::Template, :template, 4
|
68
|
+
end
|
69
|
+
|
70
|
+
class CreateViewSubmission
|
71
|
+
class Template
|
72
|
+
optional :string, :category, 1
|
73
|
+
optional :string, :has_photo, 2
|
74
|
+
end
|
75
|
+
|
76
|
+
optional ::Mason::Create, :form, 3
|
77
|
+
optional ::CreateViewSubmission::Template, :template, 4
|
78
|
+
end
|
79
|
+
|