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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +5 -2
  4. data/brief.gemspec +1 -0
  5. data/lib/brief/briefcase.rb +12 -0
  6. data/lib/brief/document/front_matter.rb +13 -1
  7. data/lib/brief/document.rb +52 -3
  8. data/lib/brief/model/serializers.rb +19 -0
  9. data/lib/brief/model.rb +21 -1
  10. data/lib/brief/repository.rb +30 -1
  11. data/lib/brief/server/gateway.rb +36 -0
  12. data/lib/brief/server/handlers/action.rb +28 -0
  13. data/lib/brief/server/handlers/browse.rb +8 -0
  14. data/lib/brief/server/handlers/info.rb +7 -0
  15. data/lib/brief/server/handlers/modify.rb +101 -0
  16. data/lib/brief/server/handlers/schema.rb +21 -0
  17. data/lib/brief/server/handlers/show.rb +35 -0
  18. data/lib/brief/server/route.rb +75 -0
  19. data/lib/brief/server.rb +25 -0
  20. data/lib/brief/version.rb +1 -1
  21. data/lib/brief.rb +2 -0
  22. data/spec/acceptance/browsing_spec.rb +15 -0
  23. data/spec/acceptance/modifying_spec.rb +29 -0
  24. data/spec/acceptance/schema_spec.rb +13 -0
  25. data/spec/acceptance/showing_spec.rb +18 -0
  26. data/spec/fixtures/example/docs/concept.html.md +4 -1
  27. data/spec/fixtures/example/docs/{epic.html.md → epics/epic.html.md} +0 -0
  28. data/spec/lib/brief/document_spec.rb +23 -3
  29. data/spec/lib/brief/model_spec.rb +4 -0
  30. data/spec/lib/brief/rendering_spec.rb +2 -3
  31. data/spec/lib/brief/repository_spec.rb +6 -0
  32. data/spec/lib/brief/server/gateway_spec.rb +18 -0
  33. data/spec/lib/brief/server/route_spec.rb +44 -0
  34. data/spec/lib/brief/structure_spec.rb +1 -2
  35. data/spec/spec_helper.rb +8 -1
  36. data/spec/support/test_helpers.rb +22 -0
  37. metadata +40 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec9ccb7e1861ffc357e84038673da7f6505023b0
4
- data.tar.gz: c54d945e486627df421b55e4b1bbccba9f9deb41
3
+ metadata.gz: c9072da715286e14af1d9bacfa63da385030de95
4
+ data.tar.gz: 1e46844b92f38e9ef77db154599e29c2e20f2d24
5
5
  SHA512:
6
- metadata.gz: afc280b0d00cb5ad6bbcecd7b739d8b9f6bb74ed0c18cf03de02bef8b625d16ffccedd62f3a2f067ae0ec3971be80e6911e6350614ba984414de4f2d87b45d5c
7
- data.tar.gz: d49a83e4d1afb37afa9c4ece0b1a741329399b2449acfd4212a251cd2f92f70802f6e06844f31a8dd056b2b544d1dd6fe4571758ee56030e140cdb8625f693c0
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.3.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.2.1)
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
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency "pry"
34
34
  spec.add_development_dependency "pry-nav"
35
35
  spec.add_development_dependency "rspec"
36
+ spec.add_development_dependency "rack-test"
36
37
 
37
38
  end
38
39
 
@@ -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
@@ -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
- @model_class || ((data && data.type) && Brief::Model.for_type(data.type))
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
@@ -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,8 @@
1
+ module Brief::Server::Handlers
2
+ class Browse
3
+ def self.handle(path_args, briefcase, options={})
4
+ models = Array((briefcase.send(path_args) rescue nil))
5
+ [200, {"Content-Type"=>"application/json"}, models.map(&:as_json)]
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Brief::Server::Handlers
2
+ class Info
3
+ def self.handle(*args)
4
+ [200, {}, {}]
5
+ end
6
+ end
7
+ 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"
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Brief
2
- VERSION = '1.3.2'
2
+ VERSION = '1.4.1'
3
3
  end
data/lib/brief.rb CHANGED
@@ -80,5 +80,7 @@ require 'brief/repository'
80
80
  require 'brief/model'
81
81
  require 'brief/model/definition'
82
82
  require 'brief/model/persistence'
83
+ require 'brief/model/serializers'
83
84
  require 'brief/dsl'
85
+ require 'brief/server'
84
86
  require 'brief/briefcase'
@@ -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
+
@@ -2,8 +2,21 @@ require "spec_helper"
2
2
 
3
3
  describe "The Brief Document" do
4
4
  let(:sample) do
5
- path = Brief.example_path.join("docs","epic.html.md")
6
- Brief::Document.new(path)
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
- path = Brief.example_path.join("docs","epic.html.md")
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
@@ -2,8 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe "Document Structure Information" do
4
4
  let(:sample) do
5
- path = Brief.example_path.join("docs","epic.html.md")
6
- Brief::Document.new(path)
5
+ Brief.example_document
7
6
  end
8
7
 
9
8
  let(:one) do
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
- #config.order = "random"
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.3.2
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-05 00:00:00.000000000 Z
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