brief 1.4.4 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile.lock +1 -1
  4. data/lib/brief/briefcase.rb +58 -1
  5. data/lib/brief/document/content_extractor.rb +12 -3
  6. data/lib/brief/document/rendering.rb +3 -1
  7. data/lib/brief/document/section/builder.rb +5 -3
  8. data/lib/brief/document/structure.rb +18 -1
  9. data/lib/brief/document.rb +48 -6
  10. data/lib/brief/dsl.rb +4 -0
  11. data/lib/brief/model/serializers.rb +10 -11
  12. data/lib/brief/model.rb +14 -1
  13. data/lib/brief/repository.rb +28 -1
  14. data/lib/brief/server/gateway.rb +9 -3
  15. data/lib/brief/server/handlers/aggregator.rb +8 -0
  16. data/lib/brief/server/handlers/browse.rb +1 -0
  17. data/lib/brief/server/handlers/info.rb +3 -12
  18. data/lib/brief/server/handlers/modify.rb +23 -6
  19. data/lib/brief/server/handlers/show.rb +1 -1
  20. data/lib/brief/server/route.rb +3 -0
  21. data/lib/brief/server.rb +2 -0
  22. data/lib/brief/util.rb +10 -0
  23. data/lib/brief/version.rb +1 -1
  24. data/lib/brief.rb +9 -0
  25. data/spec/acceptance/aggregators_spec.rb +8 -0
  26. data/spec/acceptance/browsing_spec.rb +11 -0
  27. data/spec/acceptance/modifying_spec.rb +28 -3
  28. data/spec/acceptance/showing_spec.rb +11 -0
  29. data/spec/fixtures/example/brief.rb +4 -0
  30. data/spec/fixtures/example/docs/concept.html.md +4 -2
  31. data/spec/fixtures/example/docs/page.html.md +10 -0
  32. data/spec/fixtures/example/models/page.rb +12 -0
  33. data/spec/fixtures/example/settings.yml +2 -0
  34. data/spec/lib/brief/briefcase_spec.rb +11 -3
  35. data/spec/lib/brief/document_spec.rb +4 -0
  36. data/spec/lib/brief/models/page_spec.rb +19 -0
  37. data/spec/lib/brief/repository_spec.rb +1 -1
  38. data/spec/lib/brief/server/gateway_spec.rb +1 -1
  39. data/spec/lib/brief/server/route_spec.rb +0 -1
  40. data/spec/spec_helper.rb +5 -0
  41. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1051f1c7f8b82c1325913b4a8daa9e113404de69
4
- data.tar.gz: cbb53d874b7f6b24d8d16164590f489d74f31fc5
3
+ metadata.gz: 79796113d0d8f8f21e6d5143efa8a8da8faa46f8
4
+ data.tar.gz: 9a788247f96dc6386db346f203acc6a6f227d207
5
5
  SHA512:
6
- metadata.gz: c7885b7fef9d6705d66a930b0e4e522bac85c53afc11560b5b4dcceb7026551c0ee1c5f585d58a0d777a230b2fde11b2062915f0412f27e8ba221781c9d0bbbf
7
- data.tar.gz: a5f1c7ee952d2b8ba5ceb3d72b324ebea099025cf03df6f7ef9f7de539b7d3d34feb6141fdf95b97231756d0883ec0bd93e88f1bdd6339dd6bb067d481bbc4c4
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brief (1.4.3)
4
+ brief (1.4.5)
5
5
  activemodel
6
6
  activesupport
7
7
  commander
@@ -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 repository.respond_to?(meth)
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 attribute_set
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
- attribute_set.key?(meth) || super
28
+ content_schema_attributes.key?(meth) || super
20
29
  end
21
30
 
22
31
  def method_missing(meth, *_args, &_block)
23
- if settings = attribute_set.fetch(meth, nil)
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
- if %w(h1 h2 h3 h4 h5 h6).include?(parent.children.first.name)
69
- parent['data-heading'] = parent.children.first.text
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
- mapping.push([current_level, bucket]) unless mapping.include?(bucket)
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
 
@@ -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
- path.open('w') {|fh| fh.write(combined_data_and_content) }
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
- path.open('w+') {|fh| fh.write(combined_data_and_content) }
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
- @briefcase = briefcase
81
+ @briefcase_root = briefcase.root
47
82
  self
48
83
  end
49
84
 
50
85
  def briefcase
51
- @briefcase || Brief.case
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
@@ -6,6 +6,10 @@ module Brief
6
6
  Brief::Configuration.instance.instance_eval(&block) if block_given?
7
7
  end
8
8
 
9
+ def view(name, &block)
10
+ Brief.views[name.to_sym] = block
11
+ end
12
+
9
13
  def define(*args, &block)
10
14
  options = args.dup.extract_options!
11
15
  name = args.first
@@ -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
- if options[:docs_path]
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
- path: doc_path.to_s,
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: {
@@ -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.for_type(type).models.to_a)
113
+ instance_variable_set("@#{plural}", Brief::Model.existing_models_for_type(type))
87
114
  instance_variable_get("@#{ plural }")
88
115
  end
89
116
  end
@@ -25,9 +25,15 @@ class Brief::Server::Gateway
25
25
 
26
26
  def call(env)
27
27
  request = Rack::Request.new(env)
28
-
29
- if request.path.match(/__info$/)
30
- return [200, {}, [@briefcases.keys.to_json]]
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
@@ -0,0 +1,8 @@
1
+ module Brief::Server::Handlers
2
+ class Aggregator
3
+ def self.handle(path, briefcase, options={})
4
+ [200, {"Content-Type"=>"application/json"}, briefcase.send(path)]
5
+ end
6
+ end
7
+ end
8
+
@@ -1,6 +1,7 @@
1
1
  module Brief::Server::Handlers
2
2
  class Browse
3
3
  def self.handle(path_args, briefcase, options={})
4
+ briefcase.purge(path_args)
4
5
  models = Array((briefcase.send(path_args) rescue nil))
5
6
  [200, {"Content-Type"=>"application/json"}, models.map(&:as_json)]
6
7
  end
@@ -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
- action = options.fetch(:action)
5
+ params = request.params.symbolize_keys
6
+ style = params.fetch(:presenter, "default")
6
7
 
7
- schema = {}
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.content = contents if contents.to_s.length > 0
60
- document.data = data if data && !data.empty?
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
- document = Brief::Document.new(path)
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
- document.contents = params[:contents] if params[:contents]
85
- document.data = (document.data || {}).merge(params[:data]) if params[:data].is_a?(Hash)
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]
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Brief
2
- VERSION = '1.4.4'
2
+ VERSION = '1.5.0'
3
3
  end
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
@@ -0,0 +1,8 @@
1
+ require "spec_helper"
2
+
3
+ describe "Document Aggregators", :type => :request do
4
+ it "runs an aggregator on the briefcase" do
5
+ get "/aggregator/custom_aggregator"
6
+ expect(json["aggregator"]).to eq("custom")
7
+ end
8
+ 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
- post "/create/epics/newly-created-epic.html.md", contents: "# Epic Title"
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", contents: "# Modified Content #{ needle }"
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
@@ -3,6 +3,10 @@ config do
3
3
  set(:templates => Pathname(File.dirname(__FILE__)).join("templates"))
4
4
  end
5
5
 
6
+ view :custom_aggregator do
7
+ {aggregator:"custom"}
8
+ end
9
+
6
10
  define "User Story" do
7
11
  meta do
8
12
  title
@@ -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 sflavhtnupa7u6zniwsl4f8u7fhi16dkddbk"
6
- needle: p2hrybcv4jp6nfuenrm671xq3cgfyp30v9vk
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.
@@ -0,0 +1,12 @@
1
+ class Brief::Page
2
+ include Brief::Model
3
+
4
+ meta do
5
+ title
6
+ end
7
+
8
+ content do
9
+ title "h1:first-of-type"
10
+ paragraph "p:first-of-type"
11
+ end
12
+ end
@@ -0,0 +1,2 @@
1
+ ---
2
+ settings: "nice"
@@ -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(2)
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(2)
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(7)
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(6)
39
+ expect(query.length).to eq(7)
40
40
  end
41
41
 
42
42
  it "limits the results to the specified size" do
@@ -13,6 +13,6 @@ describe "Briefcase Server Gateway" do
13
13
 
14
14
  expect(status).to eq(200)
15
15
  expect(headers["Access-Control-Allow-Origin"]).to eq("*")
16
- expect(json.length).to eq(2)
16
+ expect(json.length).to eq(1)
17
17
  end
18
18
  end
@@ -1,7 +1,6 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Brief::Server::Route do
4
-
5
4
  it "routes to the browse handler" do
6
5
  expect(handler_for("/browse/epics")).to eq(Brief::Server::Handlers::Browse)
7
6
  end
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.4
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-19 00:00:00.000000000 Z
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