brief 1.3.2 → 1.4.1
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.
- 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
|