brief 1.4.4 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/lib/brief/briefcase.rb +58 -1
- data/lib/brief/document/content_extractor.rb +12 -3
- data/lib/brief/document/rendering.rb +3 -1
- data/lib/brief/document/section/builder.rb +5 -3
- data/lib/brief/document/structure.rb +18 -1
- data/lib/brief/document.rb +48 -6
- data/lib/brief/dsl.rb +4 -0
- data/lib/brief/model/serializers.rb +10 -11
- data/lib/brief/model.rb +14 -1
- data/lib/brief/repository.rb +28 -1
- data/lib/brief/server/gateway.rb +9 -3
- data/lib/brief/server/handlers/aggregator.rb +8 -0
- data/lib/brief/server/handlers/browse.rb +1 -0
- data/lib/brief/server/handlers/info.rb +3 -12
- data/lib/brief/server/handlers/modify.rb +23 -6
- data/lib/brief/server/handlers/show.rb +1 -1
- data/lib/brief/server/route.rb +3 -0
- data/lib/brief/server.rb +2 -0
- data/lib/brief/util.rb +10 -0
- data/lib/brief/version.rb +1 -1
- data/lib/brief.rb +9 -0
- data/spec/acceptance/aggregators_spec.rb +8 -0
- data/spec/acceptance/browsing_spec.rb +11 -0
- data/spec/acceptance/modifying_spec.rb +28 -3
- data/spec/acceptance/showing_spec.rb +11 -0
- data/spec/fixtures/example/brief.rb +4 -0
- data/spec/fixtures/example/docs/concept.html.md +4 -2
- data/spec/fixtures/example/docs/page.html.md +10 -0
- data/spec/fixtures/example/models/page.rb +12 -0
- data/spec/fixtures/example/settings.yml +2 -0
- data/spec/lib/brief/briefcase_spec.rb +11 -3
- data/spec/lib/brief/document_spec.rb +4 -0
- data/spec/lib/brief/models/page_spec.rb +19 -0
- data/spec/lib/brief/repository_spec.rb +1 -1
- data/spec/lib/brief/server/gateway_spec.rb +1 -1
- data/spec/lib/brief/server/route_spec.rb +0 -1
- data/spec/spec_helper.rb +5 -0
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79796113d0d8f8f21e6d5143efa8a8da8faa46f8
|
4
|
+
data.tar.gz: 9a788247f96dc6386db346f203acc6a6f227d207
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01fd22902ed25d4660b8e1b72cd6045541895459f766ad71ee58990703f1a4de4e7075d81e9f883249fe6df0ee1a7e7a762edddfdb63d1aa40dbf588f35912df
|
7
|
+
data.tar.gz: ab24732776aa546dc91b46a1ff5d21ddbcd8081d311bf9733fd259b1e017db84c88a776c167b8507168ba091b399fe5895e9af5e7b79c6ee50fe6cd75c8419a1
|
data/CHANGELOG.md
CHANGED
@@ -117,3 +117,7 @@ attributes.
|
|
117
117
|
- Brief::Server provides a REST interface to a briefcase
|
118
118
|
- Brief::Server::Gateway provdies a REST wrapper around a folder of
|
119
119
|
briefcases
|
120
|
+
|
121
|
+
### 1.4.5
|
122
|
+
- Introducing a new DSL to define aggregator methods on the briefcase
|
123
|
+
- Aggregators have a REST interface
|
data/Gemfile.lock
CHANGED
data/lib/brief/briefcase.rb
CHANGED
@@ -14,6 +14,60 @@ module Brief
|
|
14
14
|
if Brief.case.nil?
|
15
15
|
Brief.case = self
|
16
16
|
end
|
17
|
+
|
18
|
+
Brief.cases[root] ||= self
|
19
|
+
end
|
20
|
+
|
21
|
+
def present(style="default", params={})
|
22
|
+
if respond_to?("as_#{style}")
|
23
|
+
send("as_#{style}", params)
|
24
|
+
elsif Brief.views.key?(style.to_sym)
|
25
|
+
block = Brief.views[style.to_sym]
|
26
|
+
block.call(self, params)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def settings
|
31
|
+
@settings ||= settings!
|
32
|
+
end
|
33
|
+
|
34
|
+
def settings!
|
35
|
+
if root.join("settings.yml").exist?
|
36
|
+
y = YAML.load(root.join("settings.yml").read) rescue nil
|
37
|
+
(y || {}).to_mash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def as_default(params={})
|
42
|
+
params.symbolize_keys!
|
43
|
+
|
44
|
+
model_settings = {
|
45
|
+
docs_path: docs_path
|
46
|
+
}
|
47
|
+
|
48
|
+
model_settings[:rendered] = !!(params.key?(:rendered))
|
49
|
+
model_settings[:content] = !!(params.key?(:content))
|
50
|
+
|
51
|
+
all = all_models.compact
|
52
|
+
|
53
|
+
schema = all.map(&:class).uniq.compact
|
54
|
+
.map(&:to_schema)
|
55
|
+
.reduce({}) {|m, k| m[k[:type_alias]] = k; m }
|
56
|
+
|
57
|
+
models = all.map {|m| m.as_json(model_settings) }
|
58
|
+
|
59
|
+
{
|
60
|
+
views: Brief.views.keys,
|
61
|
+
key: briefcase.folder_name.to_s.parameterize,
|
62
|
+
name: briefcase.folder_name.to_s.titlecase,
|
63
|
+
schema: schema,
|
64
|
+
models: models,
|
65
|
+
settings: settings
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def as_full_export
|
70
|
+
as_default(content: true, rendered: true)
|
17
71
|
end
|
18
72
|
|
19
73
|
def use(module_type=:app, module_id)
|
@@ -111,7 +165,10 @@ module Brief
|
|
111
165
|
end
|
112
166
|
|
113
167
|
def method_missing(meth, *args, &block)
|
114
|
-
if
|
168
|
+
if Brief.views.key?(meth.to_sym)
|
169
|
+
block = Brief.views[meth.to_sym]
|
170
|
+
block.call(self, args.extract_options!)
|
171
|
+
elsif repository.respond_to?(meth)
|
115
172
|
repository.send(meth, *args, &block)
|
116
173
|
else
|
117
174
|
super
|
@@ -11,16 +11,25 @@ module Brief
|
|
11
11
|
Brief::Model.for_type(@model_type)
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def content_schema_attributes
|
15
15
|
model_class.definition.content_schema.attributes
|
16
16
|
end
|
17
17
|
|
18
|
+
def extracted_content_data
|
19
|
+
me = self
|
20
|
+
content_schema_attributes.keys.reduce({}.to_mash) do |memo, attr|
|
21
|
+
val = me.send(attr) rescue nil
|
22
|
+
memo[attr] = val if val
|
23
|
+
memo
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
18
27
|
def respond_to?(meth)
|
19
|
-
|
28
|
+
content_schema_attributes.key?(meth) || super
|
20
29
|
end
|
21
30
|
|
22
31
|
def method_missing(meth, *_args, &_block)
|
23
|
-
if settings =
|
32
|
+
if settings = content_schema_attributes.fetch(meth, nil)
|
24
33
|
if settings.args.length == 1 && settings.args.first.is_a?(String)
|
25
34
|
selector = settings.args.first
|
26
35
|
matches = document.css(selector)
|
@@ -37,12 +37,14 @@ module Brief
|
|
37
37
|
# attributes, and other things to the HTML so that the output HTML retains its
|
38
38
|
# relationship to the underlying data and document structure.
|
39
39
|
def to_html(options = {})
|
40
|
-
if options[:wrap] == false
|
40
|
+
html = if options[:wrap] == false
|
41
41
|
unwrapped_html
|
42
42
|
else
|
43
43
|
wrapper = options.fetch(:wrapper, 'div')
|
44
44
|
"<#{ wrapper } data-brief-model='#{ model_class.type_alias }' data-brief-path='#{ relative_path_identifier }'>#{ unwrapped_html }</#{wrapper}>"
|
45
45
|
end
|
46
|
+
|
47
|
+
html.respond_to?(:html_safe) ? html.html_safe : html.to_s
|
46
48
|
end
|
47
49
|
|
48
50
|
def unwrapped_html
|
@@ -61,12 +61,14 @@ class Brief::Document::Section
|
|
61
61
|
@cycles += 1
|
62
62
|
end
|
63
63
|
|
64
|
-
self.nodes = source.map(&:last)
|
64
|
+
self.nodes = source.map(&:last).flatten
|
65
65
|
|
66
66
|
nodes.each do |node|
|
67
67
|
parent = node.css('section, article').first
|
68
|
-
|
69
|
-
|
68
|
+
parents_first_el = parent.children.first
|
69
|
+
|
70
|
+
if parents_first_el && %w(h1 h2 h3 h4 h5 h6).include?(parent.children.first.name)
|
71
|
+
parent['data-heading'] = parents_first_el.text
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
@@ -26,12 +26,24 @@ module Brief
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
# Markdown rendered HTML comes in the forms of a bunch of siblings,
|
30
|
+
# and no parents. We need to introduce the concept of ownership of
|
31
|
+
# sections of the document, by using the heading level (h1 - h6) as
|
32
|
+
# a form of rank.
|
33
|
+
#
|
34
|
+
# All h1 elements will 'own' the h2,h3,h4,h5,h6
|
35
|
+
# elements underneath them.
|
29
36
|
def create_wrappers
|
30
37
|
return if @elements_have_been_wrapped
|
31
38
|
|
32
39
|
elements = fragment.children
|
33
40
|
|
41
|
+
# The different groups of elements
|
34
42
|
mapping = []
|
43
|
+
|
44
|
+
# The current bucket of elements that is being
|
45
|
+
# collected, will get reset whenever it runs into
|
46
|
+
# an element that is a greater heading rank
|
35
47
|
bucket = []
|
36
48
|
|
37
49
|
current_level = Util.level(elements.first)
|
@@ -39,6 +51,8 @@ module Brief
|
|
39
51
|
elements.each_cons(2) do |element, next_element|
|
40
52
|
bucket << element
|
41
53
|
|
54
|
+
# We will have run into a greater header, so close up the bucket
|
55
|
+
# and put it into the mapping
|
42
56
|
if Util.is_header?(next_element) && Util.level(next_element) >= current_level
|
43
57
|
mapping.push([current_level, bucket])
|
44
58
|
bucket = []
|
@@ -49,7 +63,10 @@ module Brief
|
|
49
63
|
end
|
50
64
|
end
|
51
65
|
|
52
|
-
|
66
|
+
# we never ended up reaching a header, so close up and move on
|
67
|
+
if !mapping.include?(bucket)
|
68
|
+
mapping.push([current_level, bucket])
|
69
|
+
end
|
53
70
|
|
54
71
|
base_fragment = Nokogiri::HTML.fragment("<div class='brief top level' />")
|
55
72
|
|
data/lib/brief/document.rb
CHANGED
@@ -6,6 +6,10 @@ module Brief
|
|
6
6
|
|
7
7
|
attr_accessor :path, :content, :frontmatter, :raw_content
|
8
8
|
|
9
|
+
def document
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
9
13
|
def initialize(path, options = {})
|
10
14
|
if path.respond_to?(:key?) && options.empty?
|
11
15
|
@frontmatter = path.to_mash
|
@@ -13,24 +17,55 @@ module Brief
|
|
13
17
|
@path = Pathname(path)
|
14
18
|
end
|
15
19
|
|
20
|
+
if options[:Test]
|
21
|
+
binding.pry
|
22
|
+
end
|
23
|
+
|
16
24
|
@options = options.to_mash
|
17
25
|
|
18
26
|
if @path && self.path.exist?
|
19
|
-
@raw_content = path.read
|
27
|
+
@raw_content = self.path.read
|
20
28
|
load_frontmatter
|
21
29
|
elsif options[:contents]
|
22
30
|
@raw_content = options[:contents]
|
23
31
|
end
|
24
32
|
|
33
|
+
register_model_instance if self.path && self.path.exist?
|
34
|
+
end
|
35
|
+
|
36
|
+
def register_model_instance
|
25
37
|
model_class.try(:models).try(:<<, to_model) unless model_instance_registered?
|
26
38
|
end
|
27
39
|
|
40
|
+
def raw= val
|
41
|
+
@raw_set = true
|
42
|
+
@raw_content = val
|
43
|
+
#document.load_frontmatter
|
44
|
+
@raw_content
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_raw?
|
48
|
+
!!@raw_set
|
49
|
+
end
|
50
|
+
|
28
51
|
def save
|
29
|
-
|
52
|
+
if set_raw?
|
53
|
+
file_contents = raw_content
|
54
|
+
else
|
55
|
+
file_contents = combined_data_and_content
|
56
|
+
end
|
57
|
+
|
58
|
+
path.open('w') {|fh| fh.write(file_contents) }
|
30
59
|
end
|
31
60
|
|
32
61
|
def save!
|
33
|
-
|
62
|
+
if set_raw?
|
63
|
+
file_contents = raw_content
|
64
|
+
else
|
65
|
+
file_contents = combined_data_and_content
|
66
|
+
end
|
67
|
+
|
68
|
+
path.open('w+') {|fh| fh.write(file_contents) }
|
34
69
|
end
|
35
70
|
|
36
71
|
def combined_data_and_content
|
@@ -43,12 +78,12 @@ module Brief
|
|
43
78
|
end
|
44
79
|
|
45
80
|
def in_briefcase(briefcase)
|
46
|
-
@
|
81
|
+
@briefcase_root = briefcase.root
|
47
82
|
self
|
48
83
|
end
|
49
84
|
|
50
85
|
def briefcase
|
51
|
-
@
|
86
|
+
(@briefcase_root && Brief.cases[@briefcase_root]) || Brief.case
|
52
87
|
end
|
53
88
|
|
54
89
|
def sections
|
@@ -69,6 +104,9 @@ module Brief
|
|
69
104
|
end
|
70
105
|
|
71
106
|
def content
|
107
|
+
if @content.nil?
|
108
|
+
generate_content
|
109
|
+
end
|
72
110
|
@content || generate_content
|
73
111
|
end
|
74
112
|
|
@@ -114,7 +152,7 @@ module Brief
|
|
114
152
|
end
|
115
153
|
|
116
154
|
def to_model
|
117
|
-
model_class.new((data || {}).to_hash.merge(path: path, document: self)) if model_class
|
155
|
+
model_class.new((data || {}).to_hash.merge(path: path, document: self).reverse_merge(:type=>document_type)) if model_class
|
118
156
|
end
|
119
157
|
|
120
158
|
def exist?
|
@@ -172,6 +210,10 @@ module Brief
|
|
172
210
|
@fragment ||= Nokogiri::HTML.fragment(to_raw_html)
|
173
211
|
end
|
174
212
|
|
213
|
+
def type
|
214
|
+
document_type
|
215
|
+
end
|
216
|
+
|
175
217
|
def method_missing(meth, *args, &block)
|
176
218
|
if data.respond_to?(meth)
|
177
219
|
data.send(meth, *args, &block)
|
data/lib/brief/dsl.rb
CHANGED
@@ -1,15 +1,10 @@
|
|
1
1
|
module Brief::Model::Serializers
|
2
2
|
def as_json(options={})
|
3
|
+
options.symbolize_keys!
|
4
|
+
docs_path = options.fetch(:docs_path) { briefcase.docs_path }
|
5
|
+
docs_path = docs_path.to_pathname if docs_path.is_a?(String)
|
3
6
|
|
4
|
-
|
5
|
-
if path.absolute?
|
6
|
-
doc_path = path.relative_path_from(options[:docs_path])
|
7
|
-
else
|
8
|
-
doc_path = path
|
9
|
-
end
|
10
|
-
else
|
11
|
-
doc_path = path.to_s
|
12
|
-
end
|
7
|
+
doc_path = path.relative_path_from(docs_path).to_s
|
13
8
|
|
14
9
|
# TEMP
|
15
10
|
title = data.try(:[], :title) || extracted_content.try(:title) || (send(:title) rescue nil) || path.basename.to_s.gsub(/\.html.md/,'')
|
@@ -17,7 +12,8 @@ module Brief::Model::Serializers
|
|
17
12
|
|
18
13
|
{
|
19
14
|
data: data,
|
20
|
-
|
15
|
+
extracted: extracted_content_data,
|
16
|
+
path: path.to_s,
|
21
17
|
type: type,
|
22
18
|
title: title,
|
23
19
|
actions: self.class.defined_actions,
|
@@ -30,6 +26,9 @@ module Brief::Model::Serializers
|
|
30
26
|
schema_url: "/schema/#{ type }",
|
31
27
|
actions_url: "/actions/:action/#{ doc_path }"
|
32
28
|
}
|
33
|
-
}
|
29
|
+
}.tap do |h|
|
30
|
+
h[:content] = document.combined_data_and_content if options[:content]
|
31
|
+
h[:rendered] = document.to_html if options[:rendered]
|
32
|
+
end
|
34
33
|
end
|
35
34
|
end
|
data/lib/brief/model.rb
CHANGED
@@ -30,7 +30,7 @@ module Brief
|
|
30
30
|
|
31
31
|
module AccessorMethods
|
32
32
|
def data
|
33
|
-
document.data
|
33
|
+
document.data || {}.to_mash
|
34
34
|
end
|
35
35
|
|
36
36
|
def content
|
@@ -52,6 +52,10 @@ module Brief
|
|
52
52
|
super
|
53
53
|
end
|
54
54
|
end
|
55
|
+
|
56
|
+
def exists?
|
57
|
+
document && document.path && document.path.exist?
|
58
|
+
end
|
55
59
|
end
|
56
60
|
|
57
61
|
def self.classes
|
@@ -72,6 +76,11 @@ module Brief
|
|
72
76
|
table[type_alias]
|
73
77
|
end
|
74
78
|
|
79
|
+
def self.existing_models_for_type(type_alias)
|
80
|
+
klass = for_type(type_alias)
|
81
|
+
klass.models.select(&:exists?)
|
82
|
+
end
|
83
|
+
|
75
84
|
def self.for_folder_name(folder_name=nil)
|
76
85
|
folder_name = folder_name.to_s.downcase
|
77
86
|
table[folder_name.singularize] || table[folder_name]
|
@@ -99,6 +108,10 @@ module Brief
|
|
99
108
|
end
|
100
109
|
|
101
110
|
module ClassMethods
|
111
|
+
def purge
|
112
|
+
models.reject! {|model| !model.document.path.exist? }
|
113
|
+
end
|
114
|
+
|
102
115
|
def to_schema
|
103
116
|
{
|
104
117
|
schema: {
|
data/lib/brief/repository.rb
CHANGED
@@ -72,6 +72,33 @@ module Brief
|
|
72
72
|
Dir[root.join('**/*.md').to_s].map { |p| Pathname(p) }
|
73
73
|
end
|
74
74
|
|
75
|
+
def all_models
|
76
|
+
list = documents.map(&:to_model)
|
77
|
+
list.compact!
|
78
|
+
list.select!(&:exists?)
|
79
|
+
|
80
|
+
list
|
81
|
+
end
|
82
|
+
|
83
|
+
def all_models_by_type
|
84
|
+
all_models.reduce({}) do |memo, model|
|
85
|
+
(memo[model.class.type_alias] ||= []) << model if model.exists?
|
86
|
+
memo
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def purge(model_type=nil)
|
91
|
+
if model_type
|
92
|
+
plural = model_type.to_s.pluralize
|
93
|
+
|
94
|
+
if instance_variable_get("@#{ plural }")
|
95
|
+
instance_variable_set("@#{plural}",nil)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
documents.reject! {|doc| !doc.path.exist? }
|
100
|
+
end
|
101
|
+
|
75
102
|
def self.define_document_finder_methods
|
76
103
|
# Create a finder method on the repository
|
77
104
|
# which lets us find instances of models by their class name
|
@@ -83,7 +110,7 @@ module Brief
|
|
83
110
|
end
|
84
111
|
|
85
112
|
define_method("#{ plural }!") do
|
86
|
-
instance_variable_set("@#{plural}", Brief::Model.
|
113
|
+
instance_variable_set("@#{plural}", Brief::Model.existing_models_for_type(type))
|
87
114
|
instance_variable_get("@#{ plural }")
|
88
115
|
end
|
89
116
|
end
|
data/lib/brief/server/gateway.rb
CHANGED
@@ -25,9 +25,15 @@ class Brief::Server::Gateway
|
|
25
25
|
|
26
26
|
def call(env)
|
27
27
|
request = Rack::Request.new(env)
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
params = request.params.symbolize_keys
|
29
|
+
|
30
|
+
if request.path.match(/\/all$/)
|
31
|
+
presenter = params.fetch(:presenter, 'default')
|
32
|
+
return [200, {}, [
|
33
|
+
@briefcases.values.map do |bc|
|
34
|
+
bc.present(presenter, params)
|
35
|
+
end.to_json
|
36
|
+
]]
|
31
37
|
end
|
32
38
|
|
33
39
|
name = request.path.match(/\/\w+\/(\w+)/)[1] rescue nil
|
@@ -2,19 +2,10 @@ module Brief::Server::Handlers
|
|
2
2
|
class Info
|
3
3
|
def self.handle(path, briefcase, options={})
|
4
4
|
request = options.fetch(:request)
|
5
|
-
|
5
|
+
params = request.params.symbolize_keys
|
6
|
+
style = params.fetch(:presenter, "default")
|
6
7
|
|
7
|
-
|
8
|
-
models = {}
|
9
|
-
|
10
|
-
Brief::Model.classes.map do |k|
|
11
|
-
schema[k.type_alias] = k.to_schema
|
12
|
-
|
13
|
-
a = k.type_alias.to_s.pluralize
|
14
|
-
models[a] = briefcase.send(a).map {|m| m.as_json(docs_path: briefcase.docs_path) }
|
15
|
-
end
|
16
|
-
|
17
|
-
[200, {}, {name: briefcase.folder_name.to_s, schema: schema, models: models}]
|
8
|
+
[200, {}, briefcase.present(style, params)]
|
18
9
|
end
|
19
10
|
end
|
20
11
|
end
|
@@ -48,7 +48,8 @@ module Brief::Server::Handlers
|
|
48
48
|
|
49
49
|
def create
|
50
50
|
data = params[:data]
|
51
|
-
contents = params[:contents]
|
51
|
+
contents = params[:content] || params[:contents]
|
52
|
+
raw = params[:raw]
|
52
53
|
|
53
54
|
if path && path.exist?
|
54
55
|
@errors[:path] = "Path already exists"
|
@@ -56,8 +57,15 @@ module Brief::Server::Handlers
|
|
56
57
|
end
|
57
58
|
|
58
59
|
doc = Brief::Document.new(path).tap do |document|
|
59
|
-
document.
|
60
|
-
|
60
|
+
document.in_briefcase(briefcase)
|
61
|
+
|
62
|
+
if raw
|
63
|
+
document.raw = raw
|
64
|
+
elsif contents || data
|
65
|
+
document.content = contents if contents.to_s.length > 0
|
66
|
+
document.data = data if data && !data.empty?
|
67
|
+
end
|
68
|
+
|
61
69
|
document.save!
|
62
70
|
end
|
63
71
|
|
@@ -75,14 +83,23 @@ module Brief::Server::Handlers
|
|
75
83
|
end
|
76
84
|
|
77
85
|
def update
|
78
|
-
|
86
|
+
data = params[:data]
|
87
|
+
contents = params[:content] || params[:contents]
|
88
|
+
raw = params[:raw]
|
89
|
+
|
90
|
+
document = Brief::Document.new(path).in_briefcase(briefcase)
|
79
91
|
|
80
92
|
if document.nil? || !document.exist?
|
81
93
|
@errors[:document] = "No document was found at #{ path_args }"
|
82
94
|
@errors
|
83
95
|
else
|
84
|
-
|
85
|
-
|
96
|
+
if raw
|
97
|
+
document.raw = raw
|
98
|
+
elsif contents || data
|
99
|
+
document.content = contents if contents.to_s.length > 0
|
100
|
+
document.data = (document.data || {}).merge(params[:data]) if params[:data].is_a?(Hash)
|
101
|
+
end
|
102
|
+
|
86
103
|
document.save
|
87
104
|
|
88
105
|
document.to_model
|
@@ -26,7 +26,7 @@ module Brief::Server::Handlers
|
|
26
26
|
body = document.to_html
|
27
27
|
content_type = "text/html"
|
28
28
|
when document && view == "details"
|
29
|
-
body = document.to_model.as_json
|
29
|
+
body = document.to_model.as_json(request.params)
|
30
30
|
end
|
31
31
|
|
32
32
|
[code, {"Content-Type"=>content_type}, body]
|
data/lib/brief/server/route.rb
CHANGED
@@ -37,6 +37,8 @@ class Brief::Server::Route
|
|
37
37
|
case
|
38
38
|
when request.path.match(/^\/schema/)
|
39
39
|
handlers.const_get(:Schema)
|
40
|
+
when path_action == "aggregator"
|
41
|
+
handlers.const_get(:Aggregator)
|
40
42
|
when path_action == "browse"
|
41
43
|
handlers.const_get(:Browse)
|
42
44
|
when %w(create update delete remove).include?(path_action)
|
@@ -73,3 +75,4 @@ require "brief/server/handlers/browse"
|
|
73
75
|
require "brief/server/handlers/modify"
|
74
76
|
require "brief/server/handlers/schema"
|
75
77
|
require "brief/server/handlers/show"
|
78
|
+
require "brief/server/handlers/aggregator"
|
data/lib/brief/server.rb
CHANGED
@@ -12,6 +12,8 @@ class Brief::Server
|
|
12
12
|
|
13
13
|
body = body.to_json if body.is_a?(Hash)
|
14
14
|
body = body.to_json if body.is_a?(Array)
|
15
|
+
body = body.as_json.to_json if body.is_a?(Brief::Model)
|
16
|
+
|
15
17
|
body = "" if body.nil?
|
16
18
|
|
17
19
|
headers["Content-Length"] = Rack::Utils.bytesize(body).to_s
|
data/lib/brief/util.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
module Brief::Util
|
2
|
+
def self.split_doc_content(raw_content)
|
3
|
+
if raw_content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
4
|
+
content = raw_content[(Regexp.last_match[1].size + Regexp.last_match[2].size)..-1]
|
5
|
+
frontmatter = YAML.load(Regexp.last_match[1]).to_mash
|
6
|
+
[content, frontmatter]
|
7
|
+
else
|
8
|
+
[nil, {}.to_mash]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
2
12
|
def self.create_method_dispatcher_command_for(action, klass)
|
3
13
|
identifier = "#{ action } #{ klass.type_alias.to_s.pluralize }"
|
4
14
|
|
data/lib/brief/version.rb
CHANGED
data/lib/brief.rb
CHANGED
@@ -11,6 +11,11 @@ require 'yaml'
|
|
11
11
|
require 'erb'
|
12
12
|
|
13
13
|
module Brief
|
14
|
+
|
15
|
+
def self.cases
|
16
|
+
@cases ||= {}
|
17
|
+
end
|
18
|
+
|
14
19
|
def self.case=(value)
|
15
20
|
@briefcase = value
|
16
21
|
end
|
@@ -19,6 +24,10 @@ module Brief
|
|
19
24
|
@briefcase
|
20
25
|
end
|
21
26
|
|
27
|
+
def self.views
|
28
|
+
@views ||= {}
|
29
|
+
end
|
30
|
+
|
22
31
|
def self.configuration
|
23
32
|
Brief::Configuration.instance
|
24
33
|
end
|
@@ -6,6 +6,17 @@ describe "Browsing a Briefcase REST Interface", :type => :request do
|
|
6
6
|
expect(last_response.status).to eq(200)
|
7
7
|
end
|
8
8
|
|
9
|
+
it "lets me request the briefcase info view in whatever format" do
|
10
|
+
Brief.views[:special_format] = lambda do |briefcase, params|
|
11
|
+
briefcase.as_default.merge({format: "special"})
|
12
|
+
end
|
13
|
+
|
14
|
+
resp = Brief.testcase.special_format
|
15
|
+
get("/info?presenter=special_format")
|
16
|
+
|
17
|
+
expect(json["format"]).to eq(resp[:format])
|
18
|
+
end
|
19
|
+
|
9
20
|
it "shows me all of the documents for the requested type" do
|
10
21
|
get "/browse/epics"
|
11
22
|
expect(json).to be_a(Array)
|
@@ -1,19 +1,44 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe "Modifying Documents", :type => :request do
|
4
|
-
it "lets me create new documents" do
|
5
|
-
|
4
|
+
it "lets me create new documents by passing data and content" do
|
5
|
+
begin
|
6
|
+
post "/create/epics/newly-created-epic.html.md", content: "# Epic Title"
|
7
|
+
expect(json["path"]).to be_present
|
8
|
+
expect(json["path"].to_pathname).to be_exist
|
9
|
+
expect(last_response.status).to eq(200)
|
10
|
+
ensure
|
11
|
+
doc = Brief.case.docs_path.join("epics","newly-created-epic.html.md")
|
12
|
+
doc.unlink if doc.exist?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "lets me create documents passing raw yaml and content" do
|
17
|
+
begin
|
18
|
+
needle = rand(36**36).to_s(36)
|
19
|
+
data = {newly: needle, type: "epic"}.to_yaml
|
20
|
+
post "/create/epics/newly-created-epic-raw.html.md", raw: "#{ data }\n---\n\n# Epic Title"
|
21
|
+
doc = Brief::Document.new(Brief.case.docs_path.join("epics","newly-created-epic-raw.html.md"))
|
22
|
+
expect(doc.path.read).to include(needle)
|
23
|
+
expect(last_response.status).to eq(200)
|
24
|
+
ensure
|
25
|
+
doc = Brief.case.docs_path.join("epics","newly-created-epic-raw.html.md")
|
26
|
+
doc.unlink if doc.exist?
|
27
|
+
end
|
6
28
|
end
|
7
29
|
|
8
30
|
it "lets me update existing documents" do
|
9
31
|
needle = rand(36**36).to_s(36)
|
10
|
-
post "/update/concept.html.md",
|
32
|
+
post "/update/concept.html.md", content: "# Modified Content #{ needle }"
|
33
|
+
doc = Brief.case.document_at("concept.html.md")
|
34
|
+
expect(doc.content).to include(needle)
|
11
35
|
expect(last_response.status).to eq(200)
|
12
36
|
end
|
13
37
|
|
14
38
|
it "lets me update just the metadata for an existing document" do
|
15
39
|
needle = rand(36**36).to_s(36)
|
16
40
|
post "/update/concept.html.md", data: {needle: needle}
|
41
|
+
|
17
42
|
expect(last_response.status).to eq(200)
|
18
43
|
end
|
19
44
|
|
@@ -15,4 +15,15 @@ describe "Viewing a Briefcase Document", :type => :request do
|
|
15
15
|
get("/view/details/epics/epic.html.md")
|
16
16
|
expect(last_response.status).to eq(200)
|
17
17
|
end
|
18
|
+
|
19
|
+
it "includes raw content in details mode if asked" do
|
20
|
+
get("/view/details/epics/epic.html.md?content=true&rendered=true")
|
21
|
+
expect(json["rendered"]).not_to be_empty
|
22
|
+
expect(json["content"]).not_to be_empty
|
23
|
+
end
|
24
|
+
|
25
|
+
it "includes rendered content in details mode if asked" do
|
26
|
+
get("/view/details/epics/epic.html.md?rendered=true")
|
27
|
+
expect(json["rendered"]).not_to be_empty
|
28
|
+
end
|
18
29
|
end
|
@@ -2,7 +2,9 @@
|
|
2
2
|
type: concept
|
3
3
|
title: Blueprint Concept Example
|
4
4
|
subheading: A key concept to the domain model
|
5
|
-
contents: "# Modified Content
|
6
|
-
needle:
|
5
|
+
contents: "# Modified Content yl14qj9ziu9imkidvmon70us1qsnd8jgxula"
|
6
|
+
needle: 10knkzqyphkv3mzorm8g1wpnq1qi6d0mnb5n
|
7
|
+
fucker: shit
|
7
8
|
---
|
8
9
|
|
10
|
+
# Modified Content 40o17xs3mlkcv5sa0xf0aa0a83mhyvsqqsyu
|
@@ -0,0 +1,10 @@
|
|
1
|
+
---
|
2
|
+
type: page
|
3
|
+
title: Summary
|
4
|
+
---
|
5
|
+
|
6
|
+
# Summary
|
7
|
+
|
8
|
+
The Blueprint by Architects.io is a functional specifications and requirements document that joins together various materials produced by Software Architects, Domain Modelers, Researchers, UX and UI Designers, Engineers, and Product Managers. The overall purpose is to communicate design intent, vision, and goals, along with the strategy and logistics of implementation.
|
9
|
+
|
10
|
+
Our aim is to reduce the overall communication effort required to deliver products whose results more closely match the intent and goals. This requires more effective communication tools, a more effortless feedback loop, and a methodology for validating and refining the goals themselves as everyone's understanding develops.
|
@@ -11,13 +11,21 @@ describe "The Briefcase" do
|
|
11
11
|
expect(briefcase.repository).to be_a(Brief::Repository)
|
12
12
|
end
|
13
13
|
|
14
|
+
it "defines methods which combine models in arbitrary ways" do
|
15
|
+
expect(briefcase.custom_aggregator).to be_a(Hash)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "reads the settings.yml" do
|
19
|
+
expect(briefcase.settings.settings).to be_present
|
20
|
+
end
|
21
|
+
|
14
22
|
context "Model Loading" do
|
15
23
|
it "loads the model definitions from the models folder" do
|
16
|
-
expect(Brief::Model.classes.length).to eq(
|
24
|
+
expect(Brief::Model.classes.length).to eq(3)
|
17
25
|
end
|
18
26
|
|
19
27
|
it "loads the model definitions from the DSL in the config file" do
|
20
|
-
expect(Brief::Model.classes.length).to eq(
|
28
|
+
expect(Brief::Model.classes.length).to eq(3)
|
21
29
|
end
|
22
30
|
|
23
31
|
it "caches the output" do
|
@@ -29,7 +37,7 @@ describe "The Briefcase" do
|
|
29
37
|
context "Document Mappings" do
|
30
38
|
it "has all of the documents" do
|
31
39
|
expect(briefcase.epics.length).to eq(1)
|
32
|
-
expect(briefcase.documents.length).to eq(
|
40
|
+
expect(briefcase.documents.length).to eq(8)
|
33
41
|
end
|
34
42
|
end
|
35
43
|
end
|
@@ -23,6 +23,10 @@ describe "The Brief Document" do
|
|
23
23
|
expect(sample.to_html).to match(/h1.*User Stories.*h1\>/)
|
24
24
|
end
|
25
25
|
|
26
|
+
it "renders html" do
|
27
|
+
expect(Brief.page_document.to_html).to be_present
|
28
|
+
end
|
29
|
+
|
26
30
|
it "parses the html" do
|
27
31
|
expect(sample.css("h1").length).to eq(2)
|
28
32
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "The Page Document Type" do
|
4
|
+
let(:page) { Brief.page_document.to_model }
|
5
|
+
|
6
|
+
it "should have content" do
|
7
|
+
expect(page.to_html).to include("Summary")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have the paragraph" do
|
11
|
+
expect(page.paragraph).not_to be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have the right extracted content data" do
|
15
|
+
expect(page.extracted_content_data.title).to eq("Summary")
|
16
|
+
expect(page.extracted_content_data.paragraph).not_to be_nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -36,7 +36,7 @@ describe "The Brief Document Repository" do
|
|
36
36
|
|
37
37
|
it "supports different operators" do
|
38
38
|
query = repository.where(:type.neq => "epic")
|
39
|
-
expect(query.length).to eq(
|
39
|
+
expect(query.length).to eq(7)
|
40
40
|
end
|
41
41
|
|
42
42
|
it "limits the results to the specified size" do
|
data/spec/spec_helper.rb
CHANGED
@@ -13,6 +13,11 @@ module Brief
|
|
13
13
|
testcase.root
|
14
14
|
end
|
15
15
|
|
16
|
+
def self.page_document
|
17
|
+
path = Brief.example_path.join("docs","page.html.md")
|
18
|
+
Brief::Document.new(path)
|
19
|
+
end
|
20
|
+
|
16
21
|
def self.example_document
|
17
22
|
path = Brief.example_path.join("docs","epics","epic.html.md")
|
18
23
|
Brief::Document.new(path)
|
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.5.0
|
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-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
@@ -269,6 +269,7 @@ files:
|
|
269
269
|
- lib/brief/server.rb
|
270
270
|
- lib/brief/server/gateway.rb
|
271
271
|
- lib/brief/server/handlers/action.rb
|
272
|
+
- lib/brief/server/handlers/aggregator.rb
|
272
273
|
- lib/brief/server/handlers/browse.rb
|
273
274
|
- lib/brief/server/handlers/info.rb
|
274
275
|
- lib/brief/server/handlers/modify.rb
|
@@ -277,6 +278,7 @@ files:
|
|
277
278
|
- lib/brief/server/route.rb
|
278
279
|
- lib/brief/util.rb
|
279
280
|
- lib/brief/version.rb
|
281
|
+
- spec/acceptance/aggregators_spec.rb
|
280
282
|
- spec/acceptance/browsing_spec.rb
|
281
283
|
- spec/acceptance/modifying_spec.rb
|
282
284
|
- spec/acceptance/schema_spec.rb
|
@@ -284,12 +286,15 @@ files:
|
|
284
286
|
- spec/fixtures/example/brief.rb
|
285
287
|
- spec/fixtures/example/docs/concept.html.md
|
286
288
|
- spec/fixtures/example/docs/epics/epic.html.md
|
289
|
+
- spec/fixtures/example/docs/page.html.md
|
287
290
|
- spec/fixtures/example/docs/persona.html.md
|
288
291
|
- spec/fixtures/example/docs/release.html.md
|
289
292
|
- spec/fixtures/example/docs/resource.html.md
|
290
293
|
- spec/fixtures/example/docs/user_story.html.md
|
291
294
|
- spec/fixtures/example/docs/wireframe.html.md
|
292
295
|
- spec/fixtures/example/models/epic.rb
|
296
|
+
- spec/fixtures/example/models/page.rb
|
297
|
+
- spec/fixtures/example/settings.yml
|
293
298
|
- spec/fixtures/example/templates/user_story.md.erb
|
294
299
|
- spec/fixtures/structures/one.html.md
|
295
300
|
- spec/fixtures/structures/three.html.md
|
@@ -298,6 +303,7 @@ files:
|
|
298
303
|
- spec/lib/brief/document_spec.rb
|
299
304
|
- spec/lib/brief/dsl_spec.rb
|
300
305
|
- spec/lib/brief/model_spec.rb
|
306
|
+
- spec/lib/brief/models/page_spec.rb
|
301
307
|
- spec/lib/brief/persistence_spec.rb
|
302
308
|
- spec/lib/brief/rendering_spec.rb
|
303
309
|
- spec/lib/brief/repository_spec.rb
|
@@ -334,6 +340,7 @@ signing_key:
|
|
334
340
|
specification_version: 4
|
335
341
|
summary: Brief makes writing more powerful
|
336
342
|
test_files:
|
343
|
+
- spec/acceptance/aggregators_spec.rb
|
337
344
|
- spec/acceptance/browsing_spec.rb
|
338
345
|
- spec/acceptance/modifying_spec.rb
|
339
346
|
- spec/acceptance/schema_spec.rb
|
@@ -341,12 +348,15 @@ test_files:
|
|
341
348
|
- spec/fixtures/example/brief.rb
|
342
349
|
- spec/fixtures/example/docs/concept.html.md
|
343
350
|
- spec/fixtures/example/docs/epics/epic.html.md
|
351
|
+
- spec/fixtures/example/docs/page.html.md
|
344
352
|
- spec/fixtures/example/docs/persona.html.md
|
345
353
|
- spec/fixtures/example/docs/release.html.md
|
346
354
|
- spec/fixtures/example/docs/resource.html.md
|
347
355
|
- spec/fixtures/example/docs/user_story.html.md
|
348
356
|
- spec/fixtures/example/docs/wireframe.html.md
|
349
357
|
- spec/fixtures/example/models/epic.rb
|
358
|
+
- spec/fixtures/example/models/page.rb
|
359
|
+
- spec/fixtures/example/settings.yml
|
350
360
|
- spec/fixtures/example/templates/user_story.md.erb
|
351
361
|
- spec/fixtures/structures/one.html.md
|
352
362
|
- spec/fixtures/structures/three.html.md
|
@@ -355,6 +365,7 @@ test_files:
|
|
355
365
|
- spec/lib/brief/document_spec.rb
|
356
366
|
- spec/lib/brief/dsl_spec.rb
|
357
367
|
- spec/lib/brief/model_spec.rb
|
368
|
+
- spec/lib/brief/models/page_spec.rb
|
358
369
|
- spec/lib/brief/persistence_spec.rb
|
359
370
|
- spec/lib/brief/rendering_spec.rb
|
360
371
|
- spec/lib/brief/repository_spec.rb
|