brief 1.3.2 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +5 -2
- data/brief.gemspec +1 -0
- data/lib/brief/briefcase.rb +12 -0
- data/lib/brief/document/front_matter.rb +13 -1
- data/lib/brief/document.rb +52 -3
- data/lib/brief/model/serializers.rb +19 -0
- data/lib/brief/model.rb +21 -1
- data/lib/brief/repository.rb +30 -1
- data/lib/brief/server/gateway.rb +36 -0
- data/lib/brief/server/handlers/action.rb +28 -0
- data/lib/brief/server/handlers/browse.rb +8 -0
- data/lib/brief/server/handlers/info.rb +7 -0
- data/lib/brief/server/handlers/modify.rb +101 -0
- data/lib/brief/server/handlers/schema.rb +21 -0
- data/lib/brief/server/handlers/show.rb +35 -0
- data/lib/brief/server/route.rb +75 -0
- data/lib/brief/server.rb +25 -0
- data/lib/brief/version.rb +1 -1
- data/lib/brief.rb +2 -0
- data/spec/acceptance/browsing_spec.rb +15 -0
- data/spec/acceptance/modifying_spec.rb +29 -0
- data/spec/acceptance/schema_spec.rb +13 -0
- data/spec/acceptance/showing_spec.rb +18 -0
- data/spec/fixtures/example/docs/concept.html.md +4 -1
- data/spec/fixtures/example/docs/{epic.html.md → epics/epic.html.md} +0 -0
- data/spec/lib/brief/document_spec.rb +23 -3
- data/spec/lib/brief/model_spec.rb +4 -0
- data/spec/lib/brief/rendering_spec.rb +2 -3
- data/spec/lib/brief/repository_spec.rb +6 -0
- data/spec/lib/brief/server/gateway_spec.rb +18 -0
- data/spec/lib/brief/server/route_spec.rb +44 -0
- data/spec/lib/brief/structure_spec.rb +1 -2
- data/spec/spec_helper.rb +8 -1
- data/spec/support/test_helpers.rb +22 -0
- metadata +40 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9072da715286e14af1d9bacfa63da385030de95
|
4
|
+
data.tar.gz: 1e46844b92f38e9ef77db154599e29c2e20f2d24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86022dbd77d0576533595a4f1e7538a3c9761e93bc4039f9b2dba559b9c243b802bce6ff2ce48f82cc6d7b66adc7106dec1b74f4d5c77d22590db725e444f5a7
|
7
|
+
data.tar.gz: 3446758f09912e6f91679584233b92a6a87b642bc4237d27d51500c5c75980c82a9cb2c39a01e974d12ee425dac9cd05b7107c31ef6e81a5152ecb3342861235
|
data/CHANGELOG.md
CHANGED
@@ -111,3 +111,9 @@ attributes.
|
|
111
111
|
|
112
112
|
- Various performance fixes
|
113
113
|
- Model package loading system foundation
|
114
|
+
|
115
|
+
### 1.4.0
|
116
|
+
|
117
|
+
- Brief::Server provides a REST interface to a briefcase
|
118
|
+
- Brief::Server::Gateway provdies a REST wrapper around a folder of
|
119
|
+
briefcases
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
brief (1.
|
4
|
+
brief (1.4.1)
|
5
5
|
activemodel
|
6
6
|
activesupport
|
7
7
|
commander
|
@@ -33,7 +33,7 @@ GEM
|
|
33
33
|
coderay (1.1.0)
|
34
34
|
coercible (1.0.0)
|
35
35
|
descendants_tracker (~> 0.0.1)
|
36
|
-
commander (4.
|
36
|
+
commander (4.3.0)
|
37
37
|
highline (~> 1.6.11)
|
38
38
|
descendants_tracker (0.0.4)
|
39
39
|
thread_safe (~> 0.3, >= 0.3.1)
|
@@ -66,6 +66,8 @@ GEM
|
|
66
66
|
pry-nav (0.2.4)
|
67
67
|
pry (>= 0.9.10, < 0.11.0)
|
68
68
|
rack (1.6.0)
|
69
|
+
rack-test (0.6.3)
|
70
|
+
rack (>= 1.0)
|
69
71
|
rake (10.4.2)
|
70
72
|
redcarpet (3.2.2)
|
71
73
|
rspec (3.1.0)
|
@@ -101,5 +103,6 @@ DEPENDENCIES
|
|
101
103
|
bundler (~> 1.3)
|
102
104
|
pry
|
103
105
|
pry-nav
|
106
|
+
rack-test
|
104
107
|
rake
|
105
108
|
rspec
|
data/brief.gemspec
CHANGED
data/lib/brief/briefcase.rb
CHANGED
@@ -30,6 +30,14 @@ module Brief
|
|
30
30
|
Brief::Configuration.instance
|
31
31
|
end
|
32
32
|
|
33
|
+
def server(options={})
|
34
|
+
@server ||= Brief::Server.new(self, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def folder_name
|
38
|
+
root.basename
|
39
|
+
end
|
40
|
+
|
33
41
|
# Loads the configuration for this briefcase, either from the current working directory
|
34
42
|
# or the configured path for the configuration file.
|
35
43
|
def load_configuration
|
@@ -37,6 +45,10 @@ module Brief
|
|
37
45
|
root.join('brief.rb')
|
38
46
|
end
|
39
47
|
|
48
|
+
if config_path.is_a?(String)
|
49
|
+
config_path = root.join(config_path)
|
50
|
+
end
|
51
|
+
|
40
52
|
if uses_app?
|
41
53
|
instance_eval(app_path.join("config.rb").read)
|
42
54
|
end
|
@@ -3,8 +3,18 @@ module Brief
|
|
3
3
|
module FrontMatter
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
def frontmatter= attributes
|
7
|
+
@frontmatter = attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
def data= attributes
|
11
|
+
self.frontmatter= attributes
|
12
|
+
end
|
13
|
+
|
6
14
|
def frontmatter
|
7
|
-
@frontmatter || load_frontmatter
|
15
|
+
(@frontmatter || load_frontmatter).tap do |d|
|
16
|
+
d[:type] ||= parent_folder_name && parent_folder_name.to_s.singularize if parent_folder_name && parent_folder_name.length > 0
|
17
|
+
end
|
8
18
|
end
|
9
19
|
|
10
20
|
def frontmatter_line_count
|
@@ -19,6 +29,8 @@ module Brief
|
|
19
29
|
@frontmatter_line_count = Regexp.last_match[1].lines.size
|
20
30
|
@raw_frontmatter = Regexp.last_match[1]
|
21
31
|
@frontmatter = YAML.load(Regexp.last_match[1]).to_mash
|
32
|
+
else
|
33
|
+
@frontmatter = {}.to_mash
|
22
34
|
end
|
23
35
|
end
|
24
36
|
end
|
data/lib/brief/document.rb
CHANGED
@@ -25,10 +25,32 @@ module Brief
|
|
25
25
|
model_class.try(:models).try(:<<, to_model) unless model_instance_registered?
|
26
26
|
end
|
27
27
|
|
28
|
+
def save
|
29
|
+
path.open('w') {|fh| fh.write(combined_data_and_content) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def save!
|
33
|
+
path.open('w+') {|fh| fh.write(combined_data_and_content) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def combined_data_and_content
|
37
|
+
return content if data.nil? || data.empty?
|
38
|
+
frontmatter.to_hash.to_yaml + "---\n\n#{ content }"
|
39
|
+
end
|
40
|
+
|
28
41
|
def data
|
29
42
|
frontmatter
|
30
43
|
end
|
31
44
|
|
45
|
+
def in_briefcase(briefcase)
|
46
|
+
@briefcase = briefcase
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def briefcase
|
51
|
+
@briefcase || Brief.case
|
52
|
+
end
|
53
|
+
|
32
54
|
def sections
|
33
55
|
mappings = model_class.section_mappings
|
34
56
|
|
@@ -42,6 +64,10 @@ module Brief
|
|
42
64
|
@sections
|
43
65
|
end
|
44
66
|
|
67
|
+
def content= value
|
68
|
+
@content = value
|
69
|
+
end
|
70
|
+
|
45
71
|
def content
|
46
72
|
@content || generate_content
|
47
73
|
end
|
@@ -88,11 +114,34 @@ module Brief
|
|
88
114
|
end
|
89
115
|
|
90
116
|
def to_model
|
91
|
-
model_class.new(data.to_hash.merge(path: path, document: self)) if model_class
|
117
|
+
model_class.new((data || {}).to_hash.merge(path: path, document: self)) if model_class
|
118
|
+
end
|
119
|
+
|
120
|
+
def exist?
|
121
|
+
path && path.exist?
|
92
122
|
end
|
93
123
|
|
94
124
|
def model_class
|
95
|
-
|
125
|
+
case
|
126
|
+
when @model_class
|
127
|
+
@model_class
|
128
|
+
when data && data.type
|
129
|
+
Brief::Model.for_type(data.type)
|
130
|
+
when parent_folder_name.length > 0
|
131
|
+
Brief::Model.for_folder_name(parent_folder_name)
|
132
|
+
else
|
133
|
+
raise 'Could not determine the model class to use for this document. Specify the type, or put it in a folder that maps to the correct type.'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def document_type
|
138
|
+
existing = data && data.type
|
139
|
+
return existing if existing
|
140
|
+
parent_folder_name.try(:singularize)
|
141
|
+
end
|
142
|
+
|
143
|
+
def parent_folder_name
|
144
|
+
path.parent.basename.to_s.downcase
|
96
145
|
end
|
97
146
|
|
98
147
|
# Each model class tracks the instances of the models created
|
@@ -105,7 +154,7 @@ module Brief
|
|
105
154
|
end
|
106
155
|
|
107
156
|
def respond_to?(method)
|
108
|
-
super || data.respond_to?(method) || data.key?(method)
|
157
|
+
super || (data && data.respond_to?(method)) || (data && data.key?(method))
|
109
158
|
end
|
110
159
|
|
111
160
|
def structure
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Brief::Model::Serializers
|
2
|
+
def as_json
|
3
|
+
{
|
4
|
+
data: data,
|
5
|
+
path: path.to_s,
|
6
|
+
type: type,
|
7
|
+
actions: self.class.defined_actions,
|
8
|
+
urls: {
|
9
|
+
view_content_url: "/view/content/#{ path }",
|
10
|
+
view_rendered_url: "/view/rendered/#{ path }",
|
11
|
+
view_details_url: "/view/details/#{ path }",
|
12
|
+
update_url: "/update/#{ path }",
|
13
|
+
remove_url: "/remove/#{ path }",
|
14
|
+
schema_url: "/schema/#{ type }",
|
15
|
+
actions_url: "/actions/:action/#{ path }"
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
data/lib/brief/model.rb
CHANGED
@@ -12,6 +12,7 @@ module Brief
|
|
12
12
|
include Initializers
|
13
13
|
include AccessorMethods
|
14
14
|
include Persistence
|
15
|
+
include Serializers
|
15
16
|
|
16
17
|
class_attribute :models, :after_initialization_hooks
|
17
18
|
|
@@ -45,7 +46,7 @@ module Brief
|
|
45
46
|
if document.respond_to?(meth)
|
46
47
|
document.send(meth)
|
47
48
|
else
|
48
|
-
document.data.key?(meth) ? data[meth] : extracted.send(meth)
|
49
|
+
document.data && document.data.key?(meth) ? data[meth] : extracted.send(meth)
|
49
50
|
end
|
50
51
|
else
|
51
52
|
super
|
@@ -63,10 +64,19 @@ module Brief
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
67
|
+
def self.lookup(type_alias)
|
68
|
+
for_type(type_alias) || for_folder_name(type_alias) || for_type(type_alias.singularize)
|
69
|
+
end
|
70
|
+
|
66
71
|
def self.for_type(type_alias)
|
67
72
|
table[type_alias]
|
68
73
|
end
|
69
74
|
|
75
|
+
def self.for_folder_name(folder_name=nil)
|
76
|
+
folder_name = folder_name.to_s.downcase
|
77
|
+
table[folder_name.singularize] || table[folder_name]
|
78
|
+
end
|
79
|
+
|
70
80
|
def self.lookup_class_from_args(args = [])
|
71
81
|
args = Array(args)
|
72
82
|
|
@@ -89,6 +99,16 @@ module Brief
|
|
89
99
|
end
|
90
100
|
|
91
101
|
module ClassMethods
|
102
|
+
def to_schema
|
103
|
+
{
|
104
|
+
content: definition.content_schema,
|
105
|
+
metadata: definition.metadata_schema,
|
106
|
+
class_name: to_s,
|
107
|
+
type_alias: type_alias,
|
108
|
+
name: name
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
92
112
|
def has_actions?
|
93
113
|
definition.has_actions?
|
94
114
|
end
|
data/lib/brief/repository.rb
CHANGED
@@ -16,6 +16,31 @@ module Brief
|
|
16
16
|
load_documents
|
17
17
|
end
|
18
18
|
|
19
|
+
def document_at(path)
|
20
|
+
path = normalize_path(path)
|
21
|
+
Brief::Document.new(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def documents_at!(*paths)
|
25
|
+
documents_at(*paths).select {|doc| doc.path.exist? }
|
26
|
+
end
|
27
|
+
|
28
|
+
def normalize_path(p)
|
29
|
+
docs_path.join(p)
|
30
|
+
end
|
31
|
+
|
32
|
+
def documents_at(*paths)
|
33
|
+
paths.compact!
|
34
|
+
|
35
|
+
paths.map! {|p| normalize_path(p) }
|
36
|
+
|
37
|
+
paths.map {|p| p && Brief::Document.new(p)}
|
38
|
+
end
|
39
|
+
|
40
|
+
def models_at(*paths)
|
41
|
+
documents_at(*paths).map(&:to_model)
|
42
|
+
end
|
43
|
+
|
19
44
|
def documents
|
20
45
|
return @documents if @documents
|
21
46
|
load_documents
|
@@ -33,9 +58,13 @@ module Brief
|
|
33
58
|
briefcase.root
|
34
59
|
end
|
35
60
|
|
61
|
+
def docs_path
|
62
|
+
briefcase.docs_path
|
63
|
+
end
|
64
|
+
|
36
65
|
def load_documents
|
37
66
|
@documents = document_paths.map do |path|
|
38
|
-
Brief::Document.new(path)
|
67
|
+
Brief::Document.new(path).in_briefcase(briefcase)
|
39
68
|
end
|
40
69
|
end
|
41
70
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Brief::Server::Gateway
|
2
|
+
attr_reader :root
|
3
|
+
|
4
|
+
def initialize(options={})
|
5
|
+
@root = options.fetch(:root)
|
6
|
+
@briefcases = {}
|
7
|
+
@briefcase_options = options.fetch(:briefcase_options, {})
|
8
|
+
load_briefcases
|
9
|
+
end
|
10
|
+
|
11
|
+
def briefcase_options
|
12
|
+
(@briefcase_options || {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_briefcases
|
16
|
+
config_path = briefcase_options.fetch(:config_path, "brief.rb")
|
17
|
+
|
18
|
+
root.children.select(&:directory?).each do |dir|
|
19
|
+
if dir.join(config_path).exist?
|
20
|
+
slug = dir.basename.to_s.parameterize
|
21
|
+
@briefcases[slug] ||= Brief::Briefcase.new(briefcase_options.merge(root: dir))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
request = Rack::Request.new(env)
|
28
|
+
name = request.path.match(/\/\w+\/(\w+)/)[1] rescue nil
|
29
|
+
|
30
|
+
if name && @briefcases[name]
|
31
|
+
@briefcases[name].server.call(env)
|
32
|
+
else
|
33
|
+
[404, {}, ["Not found: #{ name } -- #{ request.path }"]]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Brief::Server::Handlers
|
2
|
+
class Action
|
3
|
+
def self.handle(path_args, briefcase, options={})
|
4
|
+
parts = path_args.split("/")
|
5
|
+
action = parts.shift
|
6
|
+
path = parts.join("/")
|
7
|
+
|
8
|
+
document = briefcase.document_at(path)
|
9
|
+
|
10
|
+
headers = {
|
11
|
+
"Content-Type" => "application/json"
|
12
|
+
}
|
13
|
+
|
14
|
+
if !document
|
15
|
+
return [404,headers,{error:"Could not find a document at this path"}]
|
16
|
+
end
|
17
|
+
|
18
|
+
model = document.to_model
|
19
|
+
|
20
|
+
if !model.class.defined_actions.include?(action.to_sym)
|
21
|
+
[400, headers, {error:"Invalid action: #{ action }"}]
|
22
|
+
else
|
23
|
+
model.send(action)
|
24
|
+
[200, headers, model.as_json]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Brief::Server::Handlers
|
2
|
+
class Modify
|
3
|
+
def self.handle(path_args, briefcase, options={})
|
4
|
+
action = options.fetch(:action)
|
5
|
+
request = options.fetch(:request)
|
6
|
+
|
7
|
+
writer = Writer.new(briefcase, path_args, request.params.symbolize_keys)
|
8
|
+
|
9
|
+
headers = {"Content-Type"=>"application/json"}
|
10
|
+
|
11
|
+
response = writer.run(action)
|
12
|
+
|
13
|
+
if writer.has_errors?
|
14
|
+
[500, headers, {errors: writer.errors, path: path_args}]
|
15
|
+
else
|
16
|
+
[200, headers, response]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Writer
|
21
|
+
attr_accessor :briefcase, :params, :path
|
22
|
+
|
23
|
+
def initialize(briefcase, path_args, params)
|
24
|
+
@params = params
|
25
|
+
@briefcase = briefcase
|
26
|
+
@errors = {}
|
27
|
+
@path = briefcase.normalize_path(path_args)
|
28
|
+
|
29
|
+
raise "Invalid path for document write" unless @path
|
30
|
+
end
|
31
|
+
|
32
|
+
def run(action)
|
33
|
+
if !respond_to?(action)
|
34
|
+
@errors[:action] = "invalid"
|
35
|
+
return @errors
|
36
|
+
end
|
37
|
+
|
38
|
+
send(action)
|
39
|
+
end
|
40
|
+
|
41
|
+
def ok?
|
42
|
+
@errors.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_errors?
|
46
|
+
not ok?
|
47
|
+
end
|
48
|
+
|
49
|
+
def create
|
50
|
+
data = params[:data]
|
51
|
+
contents = params[:contents]
|
52
|
+
|
53
|
+
if path && path.exist?
|
54
|
+
@errors[:path] = "Path already exists"
|
55
|
+
return @errors
|
56
|
+
end
|
57
|
+
|
58
|
+
doc = Brief::Document.new(path).tap do |document|
|
59
|
+
document.content = contents if contents.to_s.length > 0
|
60
|
+
document.data = data if data && !data.empty?
|
61
|
+
document.save!
|
62
|
+
end
|
63
|
+
|
64
|
+
doc.to_model
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove
|
68
|
+
doc = Brief::Document.new(path)
|
69
|
+
doc.path.unlink rescue nil
|
70
|
+
|
71
|
+
{
|
72
|
+
success: ok?,
|
73
|
+
path: path
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def update
|
78
|
+
document = Brief::Document.new(path)
|
79
|
+
|
80
|
+
if document.nil? || !document.exist?
|
81
|
+
@errors[:document] = "No document was found at #{ path_args }"
|
82
|
+
@errors
|
83
|
+
else
|
84
|
+
document.contents = params[:contents] if params[:contents]
|
85
|
+
document.data = (document.data || {}).merge(params[:data]) if params[:data].is_a?(Hash)
|
86
|
+
document.save
|
87
|
+
|
88
|
+
document.to_model
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete
|
93
|
+
remove
|
94
|
+
end
|
95
|
+
|
96
|
+
def errors
|
97
|
+
@errors || {}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Brief::Server::Handlers
|
2
|
+
class Schema
|
3
|
+
def self.handle(path_args, briefcase, options)
|
4
|
+
request = options.fetch(:request)
|
5
|
+
|
6
|
+
headers = {"Content-Type"=>"application/json"}
|
7
|
+
|
8
|
+
if request.path == "/schema"
|
9
|
+
[200, headers, Brief::Model.classes.map(&:to_schema)]
|
10
|
+
elsif request.path.match(/^\/schema\/(.+)$/)
|
11
|
+
requested = request.path.split("/").last
|
12
|
+
|
13
|
+
if model = Brief::Model.lookup(requested)
|
14
|
+
[200, headers, model.to_schema]
|
15
|
+
else
|
16
|
+
[404, headers, {error: "Can not find model class matching #{ requested }"}]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Brief::Server::Handlers
|
2
|
+
class Show
|
3
|
+
def self.handle(path_args, briefcase, options={})
|
4
|
+
action = options.fetch(:action)
|
5
|
+
request = options.fetch(:request)
|
6
|
+
parts = path_args.split("/")
|
7
|
+
view = parts.shift.to_s.downcase
|
8
|
+
path = parts.join("/")
|
9
|
+
|
10
|
+
document = briefcase.document_at(path) rescue nil
|
11
|
+
|
12
|
+
code = 200
|
13
|
+
content_type = "application/json"
|
14
|
+
|
15
|
+
case
|
16
|
+
when document.nil?
|
17
|
+
code = 404
|
18
|
+
body = {error: "Not found"}
|
19
|
+
when !%w(content rendered details).include?(view)
|
20
|
+
code = 400
|
21
|
+
body = {error: "Invalid view: must be content, rendered, details" }
|
22
|
+
when document && view == "content"
|
23
|
+
body = document.combined_data_and_content
|
24
|
+
content_type = "text/plain"
|
25
|
+
when document && view == "rendered"
|
26
|
+
body = document.to_html
|
27
|
+
content_type = "text/html"
|
28
|
+
when document && view == "details"
|
29
|
+
body = document.to_model.as_json
|
30
|
+
end
|
31
|
+
|
32
|
+
[code, {"Content-Type"=>content_type}, body]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class Brief::Server::Route
|
2
|
+
attr_reader :env, :request, :briefcase, :prefix
|
3
|
+
attr_accessor :code, :headers, :body
|
4
|
+
|
5
|
+
def initialize(env, briefcase, options={})
|
6
|
+
@env = env
|
7
|
+
@request = Rack::Request.new(env)
|
8
|
+
@briefcase = briefcase
|
9
|
+
@prefix = options.fetch(:prefix) {"/briefcases/#{ briefcase.folder_name.to_s.parameterize }"}
|
10
|
+
@code = 400
|
11
|
+
@headers = {}
|
12
|
+
@body = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond
|
16
|
+
status, headers, body = response_data
|
17
|
+
[status, headers, body]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def response_data
|
23
|
+
resp = handler.handle(path_args, briefcase, request: request, action: path_action)
|
24
|
+
|
25
|
+
self.code = resp[0]
|
26
|
+
self.headers.merge(resp[1])
|
27
|
+
self.body = resp[2]
|
28
|
+
|
29
|
+
[code, headers, body]
|
30
|
+
end
|
31
|
+
|
32
|
+
def handlers
|
33
|
+
Brief::Server::Handlers
|
34
|
+
end
|
35
|
+
|
36
|
+
def handler
|
37
|
+
case
|
38
|
+
when request.path.match(/^\/schema/)
|
39
|
+
handlers.const_get(:Schema)
|
40
|
+
when path_action == "browse"
|
41
|
+
handlers.const_get(:Browse)
|
42
|
+
when %w(create update delete remove).include?(path_action)
|
43
|
+
handlers.const_get(:Modify)
|
44
|
+
when path_action == "actions"
|
45
|
+
handlers.const_get(:Action)
|
46
|
+
when path_action == "view"
|
47
|
+
handlers.const_get(:Show)
|
48
|
+
else
|
49
|
+
handlers.const_get(:Info)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def format
|
54
|
+
"application/json"
|
55
|
+
end
|
56
|
+
|
57
|
+
def without_prefix
|
58
|
+
request.path.gsub(prefix, '')
|
59
|
+
end
|
60
|
+
|
61
|
+
def path_action
|
62
|
+
without_prefix[/^\/(\w+)\/(.+)$/, 1].to_s.downcase
|
63
|
+
end
|
64
|
+
|
65
|
+
def path_args
|
66
|
+
without_prefix[/^\/(\w+)\/(.+)$/, 2]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
require "brief/server/handlers/action"
|
71
|
+
require "brief/server/handlers/info"
|
72
|
+
require "brief/server/handlers/browse"
|
73
|
+
require "brief/server/handlers/modify"
|
74
|
+
require "brief/server/handlers/schema"
|
75
|
+
require "brief/server/handlers/show"
|
data/lib/brief/server.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class Brief::Server
|
2
|
+
attr_reader :options, :briefcase
|
3
|
+
|
4
|
+
def initialize(briefcase, options={})
|
5
|
+
@briefcase = briefcase
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
request = Brief::Server::Route.new(env, briefcase, options)
|
11
|
+
status, headers, body = request.respond()
|
12
|
+
|
13
|
+
body = body.to_json if body.is_a?(Hash)
|
14
|
+
body = body.to_json if body.is_a?(Array)
|
15
|
+
body = "" if body.nil?
|
16
|
+
|
17
|
+
headers["Content-Length"] = Rack::Utils.bytesize(body)
|
18
|
+
headers["Access-Control-Allow-Origin"] = "*"
|
19
|
+
headers["Access-Control-Allow-Methods"] = "GET, POST, PUT"
|
20
|
+
|
21
|
+
[status, headers, [body]]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'brief/server/route'
|
data/lib/brief/version.rb
CHANGED
data/lib/brief.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Browsing a Briefcase REST Interface", :type => :request do
|
4
|
+
it "responds to requests" do
|
5
|
+
get "/"
|
6
|
+
expect(last_response.status).to eq(200)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "shows me all of the documents for the requested type" do
|
10
|
+
get "/browse/epics"
|
11
|
+
expect(json).to be_a(Array)
|
12
|
+
expect(json.first).to be_a(Hash)
|
13
|
+
expect(last_response.status).to eq(200)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Modifying Documents", :type => :request do
|
4
|
+
it "lets me create new documents" do
|
5
|
+
post "/create/epics/newly-created-epic.html.md", contents: "# Epic Title"
|
6
|
+
end
|
7
|
+
|
8
|
+
it "lets me update existing documents" do
|
9
|
+
needle = rand(36**36).to_s(36)
|
10
|
+
post "/update/concept.html.md", contents: "# Modified Content #{ needle }"
|
11
|
+
expect(last_response.status).to eq(200)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "lets me update just the metadata for an existing document" do
|
15
|
+
needle = rand(36**36).to_s(36)
|
16
|
+
post "/update/concept.html.md", data: {needle: needle}
|
17
|
+
expect(last_response.status).to eq(200)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "lets me remove documents" do
|
21
|
+
post "/remove/epics/newly-created-epic.html.md"
|
22
|
+
expect(last_response.status).to eq(200)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "lets me run actions on documents" do
|
26
|
+
post "/actions/custom_action/epics/epic.html.md"
|
27
|
+
expect(last_response.status).to eq(200)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Briefcase Document Schema", :type => :request do
|
4
|
+
it "gives me information about the schema" do
|
5
|
+
get("/schema")
|
6
|
+
expect(last_response.status).to eq(200)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "gives me information about a document type" do
|
10
|
+
get("/schema/epic")
|
11
|
+
expect(last_response.status).to eq(200)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Viewing a Briefcase Document", :type => :request do
|
4
|
+
it "shows the document content" do
|
5
|
+
get("/view/content/epics/epic.html.md")
|
6
|
+
expect(last_response.status).to eq(200)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "shows the rendered version of the document" do
|
10
|
+
get("/view/rendered/epics/epic.html.md")
|
11
|
+
expect(last_response.status).to eq(200)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "shows the rendered version of the document json" do
|
15
|
+
get("/view/details/epics/epic.html.md")
|
16
|
+
expect(last_response.status).to eq(200)
|
17
|
+
end
|
18
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
---
|
2
2
|
type: concept
|
3
3
|
title: Blueprint Concept Example
|
4
|
-
subheading: A key concept to the domain model
|
4
|
+
subheading: A key concept to the domain model
|
5
|
+
contents: "# Modified Content 6sq2t5qtwxij7o06183sgfzrpar4bz38kam5"
|
6
|
+
needle: b6w280kygg5axjfxsyi8gwtjvox2y6u9k785
|
5
7
|
---
|
8
|
+
|
File without changes
|
@@ -2,8 +2,21 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe "The Brief Document" do
|
4
4
|
let(:sample) do
|
5
|
-
|
6
|
-
|
5
|
+
Brief.example_document
|
6
|
+
end
|
7
|
+
|
8
|
+
it "creates a new doc if the path doesn't exist" do
|
9
|
+
begin
|
10
|
+
new_path = Brief.testcase.docs_path.join("newly-created.html.md")
|
11
|
+
doc = Brief::Document.new(new_path)
|
12
|
+
doc.data= {}
|
13
|
+
doc.content= "sup"
|
14
|
+
doc.save!
|
15
|
+
|
16
|
+
expect(doc).to be_exist
|
17
|
+
ensure
|
18
|
+
FileUtils.rm_rf(new_path)
|
19
|
+
end
|
7
20
|
end
|
8
21
|
|
9
22
|
it "renders html" do
|
@@ -14,11 +27,18 @@ describe "The Brief Document" do
|
|
14
27
|
expect(sample.css("h1").length).to eq(2)
|
15
28
|
end
|
16
29
|
|
17
|
-
|
18
30
|
it "deserializes YAML frontmatter into attributes" do
|
19
31
|
expect(sample.frontmatter.type).to eq("epic")
|
20
32
|
end
|
21
33
|
|
34
|
+
it "references the parent folder name" do
|
35
|
+
expect(sample.parent_folder_name).to eq("epics")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "can resolve the model type using the parent folder name if possible" do
|
39
|
+
expect(Brief::Model.for_folder_name(sample.parent_folder_name)).to eq(Brief::Epic)
|
40
|
+
end
|
41
|
+
|
22
42
|
context "Content Extraction" do
|
23
43
|
it "extracts content from a css selector" do
|
24
44
|
extracted = sample.extract_content(:args => ["h1:first-of-type"])
|
@@ -5,6 +5,10 @@ describe "The Brief Model" do
|
|
5
5
|
let(:epic) { briefcase.epics.first }
|
6
6
|
let(:user_story) { briefcase.user_stories.first }
|
7
7
|
|
8
|
+
it "exposes information about its schema" do
|
9
|
+
expect(epic.class.to_schema.keys).to include(:content, :metadata, :name, :class_name, :type_alias)
|
10
|
+
end
|
11
|
+
|
8
12
|
context "DSL Style Declarations" do
|
9
13
|
it "picks up a definition of 'User Story'" do
|
10
14
|
expect(briefcase.model("User Story")).to be_present
|
@@ -2,12 +2,11 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe "Brief HTML Rendering" do
|
4
4
|
let(:sample) do
|
5
|
-
|
6
|
-
Brief::Document.new(path)
|
5
|
+
Brief.example_document
|
7
6
|
end
|
8
7
|
|
9
8
|
it "wraps the document with some identifying details" do
|
10
|
-
expect(sample.to_html).to include("docs/epic.html.md")
|
9
|
+
expect(sample.to_html).to include("docs/epics/epic.html.md")
|
11
10
|
end
|
12
11
|
|
13
12
|
it "wraps the higher level headings under section elements" do
|
@@ -7,6 +7,12 @@ describe "The Brief Document Repository" do
|
|
7
7
|
expect(repository.documents).not_to be_empty
|
8
8
|
end
|
9
9
|
|
10
|
+
it "gives me the document models for paths" do
|
11
|
+
paths = ["./concept.html.md", "wireframe.html.md", "epics/epic.html.md", Brief.example_document.path.realpath]
|
12
|
+
|
13
|
+
docs = repository.documents_at(*paths)
|
14
|
+
end
|
15
|
+
|
10
16
|
context "querying api" do
|
11
17
|
it "finds the first document matching a query" do
|
12
18
|
query = repository.where(state:"active")
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "brief/server/gateway"
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "Briefcase Server Gateway" do
|
5
|
+
let(:gateway) do
|
6
|
+
Brief::Server::Gateway.new(root: Brief.spec_root.join("fixtures"))
|
7
|
+
end
|
8
|
+
|
9
|
+
it "routes requests to briefcase projects inside a folder" do
|
10
|
+
response = gateway.call(env_for("/briefcases/example/browse/epics"))
|
11
|
+
status, headers, body = response
|
12
|
+
json = JSON.parse(response.last.first)
|
13
|
+
|
14
|
+
expect(status).to eq(200)
|
15
|
+
expect(headers["Access-Control-Allow-Origin"]).to eq("*")
|
16
|
+
expect(json.length).to eq(2)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Brief::Server::Route do
|
4
|
+
|
5
|
+
it "routes to the browse handler" do
|
6
|
+
expect(handler_for("/browse/epics")).to eq(Brief::Server::Handlers::Browse)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "routes to the create handler" do
|
10
|
+
expect(handler_for("/create/epics/new.html.md")).to eq(Brief::Server::Handlers::Modify)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "routes to the update handler" do
|
14
|
+
expect(handler_for("/update/epics/new.html.md")).to eq(Brief::Server::Handlers::Modify)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "routes to the remove handler" do
|
18
|
+
expect(handler_for("/remove/epics/new.html.md")).to eq(Brief::Server::Handlers::Modify)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "routes to the actions handler" do
|
22
|
+
expect(handler_for("/actions/custom_action/epics/epic.html.md")).to eq(Brief::Server::Handlers::Action)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "routes to the schema browse handler" do
|
26
|
+
expect(handler_for("/schema")).to eq(Brief::Server::Handlers::Schema)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "routes to the schema details handler" do
|
30
|
+
expect(handler_for("/schema/epic")).to eq(Brief::Server::Handlers::Schema)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "routes to the view content handler" do
|
34
|
+
expect(handler_for("/view/content/epics/epic.html.md")).to eq(Brief::Server::Handlers::Show)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "routes to the view rendered handler" do
|
38
|
+
expect(handler_for("/view/rendered/epics/epic.html.md")).to eq(Brief::Server::Handlers::Show)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "routes to the view details handler" do
|
42
|
+
expect(handler_for("/view/details/epics/epic.html.md")).to eq(Brief::Server::Handlers::Show)
|
43
|
+
end
|
44
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'pry'
|
2
|
+
require 'rack/test'
|
2
3
|
require 'brief'
|
3
4
|
|
4
5
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
@@ -12,6 +13,11 @@ module Brief
|
|
12
13
|
testcase.root
|
13
14
|
end
|
14
15
|
|
16
|
+
def self.example_document
|
17
|
+
path = Brief.example_path.join("docs","epics","epic.html.md")
|
18
|
+
Brief::Document.new(path)
|
19
|
+
end
|
20
|
+
|
15
21
|
def self.testcase
|
16
22
|
@example ||= Brief::Briefcase.new(root:spec_root.join("fixtures","example"))
|
17
23
|
end
|
@@ -19,5 +25,6 @@ end
|
|
19
25
|
|
20
26
|
RSpec.configure do |config|
|
21
27
|
config.mock_with :rspec
|
22
|
-
|
28
|
+
config.include Rack::Test::Methods
|
29
|
+
config.include TestHelpers
|
23
30
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module TestHelpers
|
2
|
+
def app
|
3
|
+
Brief.testcase.server
|
4
|
+
end
|
5
|
+
|
6
|
+
def json
|
7
|
+
@json ||= JSON.parse(last_response.body)
|
8
|
+
end
|
9
|
+
|
10
|
+
def env_for(*args)
|
11
|
+
Rack::MockRequest.send(:env_for, *args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def route_for(*args)
|
15
|
+
env = env_for(*args)
|
16
|
+
Brief::Server::Route.new(env, Brief.testcase)
|
17
|
+
end
|
18
|
+
|
19
|
+
def handler_for(*args)
|
20
|
+
route_for(*args).send(:handler)
|
21
|
+
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brief
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Soeder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
@@ -206,6 +206,20 @@ dependencies:
|
|
206
206
|
- - ">="
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: rack-test
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
209
223
|
description: Brief is a library for developing applications whose primary interface
|
210
224
|
is the text editor
|
211
225
|
email:
|
@@ -250,12 +264,26 @@ files:
|
|
250
264
|
- lib/brief/model.rb
|
251
265
|
- lib/brief/model/definition.rb
|
252
266
|
- lib/brief/model/persistence.rb
|
267
|
+
- lib/brief/model/serializers.rb
|
253
268
|
- lib/brief/repository.rb
|
269
|
+
- lib/brief/server.rb
|
270
|
+
- lib/brief/server/gateway.rb
|
271
|
+
- lib/brief/server/handlers/action.rb
|
272
|
+
- lib/brief/server/handlers/browse.rb
|
273
|
+
- lib/brief/server/handlers/info.rb
|
274
|
+
- lib/brief/server/handlers/modify.rb
|
275
|
+
- lib/brief/server/handlers/schema.rb
|
276
|
+
- lib/brief/server/handlers/show.rb
|
277
|
+
- lib/brief/server/route.rb
|
254
278
|
- lib/brief/util.rb
|
255
279
|
- lib/brief/version.rb
|
280
|
+
- spec/acceptance/browsing_spec.rb
|
281
|
+
- spec/acceptance/modifying_spec.rb
|
282
|
+
- spec/acceptance/schema_spec.rb
|
283
|
+
- spec/acceptance/showing_spec.rb
|
256
284
|
- spec/fixtures/example/brief.rb
|
257
285
|
- spec/fixtures/example/docs/concept.html.md
|
258
|
-
- spec/fixtures/example/docs/epic.html.md
|
286
|
+
- spec/fixtures/example/docs/epics/epic.html.md
|
259
287
|
- spec/fixtures/example/docs/persona.html.md
|
260
288
|
- spec/fixtures/example/docs/release.html.md
|
261
289
|
- spec/fixtures/example/docs/resource.html.md
|
@@ -274,6 +302,8 @@ files:
|
|
274
302
|
- spec/lib/brief/rendering_spec.rb
|
275
303
|
- spec/lib/brief/repository_spec.rb
|
276
304
|
- spec/lib/brief/section_builder_spec.rb
|
305
|
+
- spec/lib/brief/server/gateway_spec.rb
|
306
|
+
- spec/lib/brief/server/route_spec.rb
|
277
307
|
- spec/lib/brief/structure_spec.rb
|
278
308
|
- spec/lib/brief/template_spec.rb
|
279
309
|
- spec/spec_helper.rb
|
@@ -304,9 +334,13 @@ signing_key:
|
|
304
334
|
specification_version: 4
|
305
335
|
summary: Brief makes writing more powerful
|
306
336
|
test_files:
|
337
|
+
- spec/acceptance/browsing_spec.rb
|
338
|
+
- spec/acceptance/modifying_spec.rb
|
339
|
+
- spec/acceptance/schema_spec.rb
|
340
|
+
- spec/acceptance/showing_spec.rb
|
307
341
|
- spec/fixtures/example/brief.rb
|
308
342
|
- spec/fixtures/example/docs/concept.html.md
|
309
|
-
- spec/fixtures/example/docs/epic.html.md
|
343
|
+
- spec/fixtures/example/docs/epics/epic.html.md
|
310
344
|
- spec/fixtures/example/docs/persona.html.md
|
311
345
|
- spec/fixtures/example/docs/release.html.md
|
312
346
|
- spec/fixtures/example/docs/resource.html.md
|
@@ -325,6 +359,8 @@ test_files:
|
|
325
359
|
- spec/lib/brief/rendering_spec.rb
|
326
360
|
- spec/lib/brief/repository_spec.rb
|
327
361
|
- spec/lib/brief/section_builder_spec.rb
|
362
|
+
- spec/lib/brief/server/gateway_spec.rb
|
363
|
+
- spec/lib/brief/server/route_spec.rb
|
328
364
|
- spec/lib/brief/structure_spec.rb
|
329
365
|
- spec/lib/brief/template_spec.rb
|
330
366
|
- spec/spec_helper.rb
|