brief 1.3.2 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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