guide 0.0.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/LICENSE +22 -0
- data/Rakefile +18 -0
- data/app/assets/javascripts/guide/application.js +21723 -0
- data/app/assets/javascripts/guide/scenario.js +12642 -0
- data/app/assets/stylesheets/guide/application.css +141 -0
- data/app/controllers/guide/base_controller.rb +83 -0
- data/app/controllers/guide/nodes_controller.rb +40 -0
- data/app/controllers/guide/scenarios_controller.rb +44 -0
- data/app/helpers/guide/application_helper.rb +4 -0
- data/app/helpers/guide/document_helper.rb +20 -0
- data/app/models/guide/arborist.rb +44 -0
- data/app/models/guide/bouncer.rb +34 -0
- data/app/models/guide/cartographer.rb +49 -0
- data/app/models/guide/content.rb +28 -0
- data/app/models/guide/default_authentication_system.rb +13 -0
- data/app/models/guide/default_authorisation_system.rb +18 -0
- data/app/models/guide/diplomat.rb +60 -0
- data/app/models/guide/document.rb +20 -0
- data/app/models/guide/endpoint_stocktaker.rb +94 -0
- data/app/models/guide/errors.rb +2 -0
- data/app/models/guide/errors/base.rb +6 -0
- data/app/models/guide/errors/interface_violation.rb +2 -0
- data/app/models/guide/errors/invalid_node.rb +2 -0
- data/app/models/guide/errors/invalid_scenario.rb +2 -0
- data/app/models/guide/errors/invalid_visibility_option.rb +2 -0
- data/app/models/guide/errors/permission_denied.rb +2 -0
- data/app/models/guide/fixture.rb +15 -0
- data/app/models/guide/fixtures.rb +3 -0
- data/app/models/guide/form_object.rb +3 -0
- data/app/models/guide/monkey.rb +47 -0
- data/app/models/guide/nobilizer.rb +9 -0
- data/app/models/guide/node.rb +67 -0
- data/app/models/guide/photographer.rb +20 -0
- data/app/models/guide/scout.rb +59 -0
- data/app/models/guide/simulator.rb +26 -0
- data/app/models/guide/structure.rb +75 -0
- data/app/models/guide/view_model.rb +37 -0
- data/app/view_models/guide/layout_view.rb +81 -0
- data/app/view_models/guide/navigation_view.rb +25 -0
- data/app/view_models/guide/node_view.rb +59 -0
- data/app/view_models/guide/scenario_layout_view.rb +49 -0
- data/app/view_models/guide/scenario_view.rb +39 -0
- data/app/views/guide/_content.html.erb +17 -0
- data/app/views/guide/common/_category.html.erb +9 -0
- data/app/views/guide/common/_footer.html.erb +21 -0
- data/app/views/guide/common/_locale_switcher.html.erb +6 -0
- data/app/views/guide/common/_navigation.html.erb +12 -0
- data/app/views/guide/common/_navigation_node.html.erb +39 -0
- data/app/views/guide/common/_page_title.html.erb +8 -0
- data/app/views/guide/common/_search.html.erb +23 -0
- data/app/views/guide/common/_unsupported_browser_message.html.erb +6 -0
- data/app/views/guide/common/_visibility_banner.html.erb +25 -0
- data/app/views/guide/nodes/_document.html.erb +3 -0
- data/app/views/guide/nodes/_scenario.html.erb +14 -0
- data/app/views/guide/nodes/_scenario_list.html.erb +10 -0
- data/app/views/guide/nodes/_structure.html.erb +26 -0
- data/app/views/guide/nodes/_template_location.html.erb +7 -0
- data/app/views/guide/nodes/show.html.erb +7 -0
- data/app/views/guide/scenarios/_scenario.html.erb +25 -0
- data/app/views/guide/scenarios/scenario/_locale_switcher.html.erb +5 -0
- data/app/views/guide/scenarios/scenario/_toolbar.html.erb +39 -0
- data/app/views/guide/scenarios/scenario/_visibility.html.erb +10 -0
- data/app/views/guide/scenarios/show.html.erb +1 -0
- data/app/views/layouts/guide/application.html.erb +76 -0
- data/app/views/layouts/guide/scenario.html.erb +38 -0
- data/app/views/layouts/guide/scenario/default.html.erb +1 -0
- data/app/views/layouts/guide/scenario/default.text.erb +1 -0
- data/config/initializers/assets.rb +1 -0
- data/config/locales/models/guide/monkey.en.yml +6 -0
- data/config/locales/views/guide/nodes/_structure.en.yml +7 -0
- data/config/routes.rb +6 -0
- data/lib/guide.rb +12 -0
- data/lib/guide/authorisation_spec_helper.rb +83 -0
- data/lib/guide/configuration.rb +29 -0
- data/lib/guide/consistency_spec_helper.rb +33 -0
- data/lib/guide/engine.rb +9 -0
- data/lib/guide/version.rb +3 -0
- data/lib/tasks/guide_tasks.rake +4 -0
- metadata +207 -16
- data/.gitignore +0 -26
- data/Gemfile +0 -4
- data/guide.gemspec +0 -12
@@ -0,0 +1,28 @@
|
|
1
|
+
class Guide::Content < Guide::Document
|
2
|
+
# The content in the Living Guide for your application is organised
|
3
|
+
# into a tree structure. This class is the root node of that tree.
|
4
|
+
# To add child nodes, use the following DSL:
|
5
|
+
#
|
6
|
+
# contains :child_node_name
|
7
|
+
#
|
8
|
+
# This example declares that the tree contains a child node named:
|
9
|
+
#
|
10
|
+
# Guide::Content::ChildNodeName
|
11
|
+
#
|
12
|
+
# You will still need to create a class for it.
|
13
|
+
#
|
14
|
+
# It is highly recommended that all of your content lives in the
|
15
|
+
# Guide::Content namespace or Guide will have trouble finding it!
|
16
|
+
#
|
17
|
+
# To specify options such as visibility, append them to the declaration:
|
18
|
+
#
|
19
|
+
# contains :child_node_name, :visibility => :unpublished
|
20
|
+
#
|
21
|
+
# Feel free to redeclare this class in your system at the following path:
|
22
|
+
#
|
23
|
+
# app/<whatever_you_want>/guide/content.rb
|
24
|
+
#
|
25
|
+
# The convention for the subdirectory in app/ is "documentation",
|
26
|
+
# but if you don't like that, you can use something else (even "models"!).
|
27
|
+
# You probably shouldn't use any other standard rails directories though.
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Guide::DefaultAuthorisationSystem
|
2
|
+
def allow?(action)
|
3
|
+
true
|
4
|
+
end
|
5
|
+
|
6
|
+
def user_is_privileged?
|
7
|
+
allow?(:view_guide_unpublished) ||
|
8
|
+
allow?(:view_guide_restricted)
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid_visibility_options
|
12
|
+
[
|
13
|
+
nil,
|
14
|
+
:unpublished,
|
15
|
+
:restricted,
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Guide::Diplomat
|
2
|
+
def initialize(session, params, default_locale)
|
3
|
+
@session = session
|
4
|
+
@params = params
|
5
|
+
@default_locale = default_locale
|
6
|
+
end
|
7
|
+
|
8
|
+
def negotiate_locale
|
9
|
+
store_new_locale_in_session if supported_locales.has_value? new_locale
|
10
|
+
|
11
|
+
best_locale
|
12
|
+
end
|
13
|
+
|
14
|
+
def supported_locales
|
15
|
+
Guide.configuration.supported_locales
|
16
|
+
end
|
17
|
+
|
18
|
+
def multiple_supported_locales?
|
19
|
+
supported_locales.keys.size > 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_locale
|
23
|
+
if supported_locales.has_value? locale_from_session
|
24
|
+
locale_from_session
|
25
|
+
else
|
26
|
+
clear_locale_from_session
|
27
|
+
@default_locale
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def best_locale
|
34
|
+
if supported_locales.has_value? temporary_locale
|
35
|
+
temporary_locale
|
36
|
+
else
|
37
|
+
current_locale
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_locale
|
42
|
+
@params[:locale]
|
43
|
+
end
|
44
|
+
|
45
|
+
def temporary_locale
|
46
|
+
@params[:temp_locale]
|
47
|
+
end
|
48
|
+
|
49
|
+
def locale_from_session
|
50
|
+
@session[:guide_locale]
|
51
|
+
end
|
52
|
+
|
53
|
+
def store_new_locale_in_session
|
54
|
+
@session[:guide_locale] = new_locale
|
55
|
+
end
|
56
|
+
|
57
|
+
def clear_locale_from_session
|
58
|
+
@session.delete(:guide_locale)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Guide::Document < Guide::Node
|
2
|
+
def template
|
3
|
+
partial || self.class.name.underscore
|
4
|
+
end
|
5
|
+
|
6
|
+
def partial
|
7
|
+
end
|
8
|
+
|
9
|
+
def stylesheets
|
10
|
+
Guide.configuration.default_stylesheets_for_documents
|
11
|
+
end
|
12
|
+
|
13
|
+
def javascripts
|
14
|
+
Guide.configuration.default_javascripts_for_documents
|
15
|
+
end
|
16
|
+
|
17
|
+
def can_be_rendered?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class Guide::EndpointStocktaker
|
2
|
+
def initialize(starting_node:)
|
3
|
+
@starting_node = starting_node
|
4
|
+
@result = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_hash
|
8
|
+
@result.tap do
|
9
|
+
add_guide_content if @result.empty?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def add_guide_content
|
16
|
+
renderable_nodes.keys.each do |node_path|
|
17
|
+
node = monkey.fetch_node(node_path)
|
18
|
+
if node.node_type == :structure
|
19
|
+
add_structure(node_path: node_path, structure: node)
|
20
|
+
else
|
21
|
+
add_document(node_path: node_path)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_structure(node_path:, structure:)
|
27
|
+
structure.formats.each do |format|
|
28
|
+
structure.scenarios.keys.each do |scenario_id|
|
29
|
+
@result[endpoint_key(node_path, scenario_id: scenario_id, scenario_format: format)] = {
|
30
|
+
"path" => url_helpers.scenario_path(
|
31
|
+
:scenario_id => scenario_id,
|
32
|
+
:scenario_format => format,
|
33
|
+
:node_path => node_path,
|
34
|
+
)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_document(node_path:)
|
41
|
+
@result[endpoint_key(node_path)] = { "path" => url_helpers.node_path(node_path) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def endpoint_key(node_path, scenario_id: nil, scenario_format: nil)
|
45
|
+
[
|
46
|
+
formatted_node_path(node_path),
|
47
|
+
formatted_scenario_id(scenario_id),
|
48
|
+
scenario_format,
|
49
|
+
].compact.join('-')
|
50
|
+
end
|
51
|
+
|
52
|
+
def formatted_node_path(node_path)
|
53
|
+
node_path.gsub('/', '.')
|
54
|
+
end
|
55
|
+
|
56
|
+
def formatted_scenario_id(scenario_id)
|
57
|
+
scenario_id = scenario_id.to_s
|
58
|
+
|
59
|
+
case scenario_id.length
|
60
|
+
when 0
|
61
|
+
nil
|
62
|
+
when 1..50
|
63
|
+
scenario_id.to_s
|
64
|
+
else
|
65
|
+
"#{scenario_id.first(24)}..#{scenario_id.last(24)}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def bouncer
|
70
|
+
@bouncer ||= BribedBouncer.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def cartographer
|
74
|
+
Guide::Cartographer.new(bouncer)
|
75
|
+
end
|
76
|
+
|
77
|
+
def monkey
|
78
|
+
@monkey ||= Guide::Monkey.new(@starting_node, bouncer)
|
79
|
+
end
|
80
|
+
|
81
|
+
def renderable_nodes
|
82
|
+
cartographer.draw_paths_to_visible_renderable_nodes(starting_node: @starting_node)
|
83
|
+
end
|
84
|
+
|
85
|
+
def url_helpers
|
86
|
+
Guide::Engine.routes.url_helpers
|
87
|
+
end
|
88
|
+
|
89
|
+
class BribedBouncer
|
90
|
+
def user_can_access?(_)
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
class Guide::Errors::Base < StandardError
|
2
|
+
# This class exists so that we can tell the difference between an error
|
3
|
+
# that is native to Guide and an error that has occurred
|
4
|
+
# outside of our domain. All explictly thrown errors in Guide
|
5
|
+
# should inherit from this.
|
6
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Guide::Fixture
|
2
|
+
# Fixtures are used to generate content for your Living Guide
|
3
|
+
# that would otherwise be repetitive, such as view models that are used
|
4
|
+
# from multiple components. Declaring these in a single place is useful
|
5
|
+
# because it helps keep a consistent interface between the fake view models
|
6
|
+
# in the Living Guide and the real view models in your application.
|
7
|
+
#
|
8
|
+
# Feel free to override or redeclare this class if you want.
|
9
|
+
# Nothing in the Guide gem depends on it.
|
10
|
+
private
|
11
|
+
|
12
|
+
def self.image_path(image_name, extension)
|
13
|
+
Guide::Photographer.new(image_name, extension).image_path
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Guide::Monkey
|
2
|
+
def initialize(starting_node, bouncer)
|
3
|
+
@starting_node = starting_node
|
4
|
+
@bouncer = bouncer
|
5
|
+
|
6
|
+
ensure_starting_node_exists
|
7
|
+
ensure_user_can_access(@starting_node)
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch_node(node_path)
|
11
|
+
current_node = @starting_node
|
12
|
+
|
13
|
+
node_ids_along_path(node_path).each do |node_id|
|
14
|
+
current_node = current_node.child_nodes[node_id]
|
15
|
+
ensure_node_exists(current_node, node_id)
|
16
|
+
ensure_user_can_access(current_node)
|
17
|
+
end
|
18
|
+
|
19
|
+
current_node
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def node_ids_along_path(node_path)
|
25
|
+
node_path.split("/").map {|node_id| node_id.to_sym }
|
26
|
+
end
|
27
|
+
|
28
|
+
def ensure_node_exists(node, node_id)
|
29
|
+
unless node.present?
|
30
|
+
raise Guide::Errors::InvalidNode,
|
31
|
+
I18n.t('.guide.monkey.invalid_node', :node_id => node_id)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def ensure_starting_node_exists
|
36
|
+
unless @starting_node.present?
|
37
|
+
raise Guide::Errors::InvalidNode,
|
38
|
+
I18n.t('.guide.monkey.invalid_starting_node')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ensure_user_can_access(node)
|
43
|
+
unless @bouncer.user_can_access?(node)
|
44
|
+
raise Guide::Errors::PermissionDenied
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Guide::Node
|
2
|
+
class_attribute :child_nodes
|
3
|
+
|
4
|
+
def self.inherited(sub_class)
|
5
|
+
sub_class.child_nodes = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.contains(id, options = {})
|
9
|
+
child_nodes[id] = child_node_class(id).new(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.child_node_class(id)
|
13
|
+
child_node_class_name = "#{self}::#{id.to_s.camelize}"
|
14
|
+
child_node_class_name.constantize
|
15
|
+
rescue NameError => name_error
|
16
|
+
raise Guide::Errors::InvalidNode,
|
17
|
+
"I can't build the tree that backs Guide because I could not load the class #{child_node_class_name}.",
|
18
|
+
name_error.backtrace
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :id, :options
|
22
|
+
|
23
|
+
def initialize(options = {})
|
24
|
+
@id = infer_id_from_class_name
|
25
|
+
@options = options
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
@id.to_s.titleize
|
30
|
+
end
|
31
|
+
|
32
|
+
def leaf_node?
|
33
|
+
self.child_nodes.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def child_nodes
|
37
|
+
self.class.child_nodes
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
self.class == other.class
|
42
|
+
end
|
43
|
+
|
44
|
+
def node_type
|
45
|
+
# for direct children, this will be :node
|
46
|
+
self.class.superclass.name.demodulize.underscore.to_sym
|
47
|
+
end
|
48
|
+
|
49
|
+
def can_be_rendered?
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def view_model
|
54
|
+
Guide::ViewModel.new
|
55
|
+
end
|
56
|
+
|
57
|
+
def image_path(image_name, extension = "png")
|
58
|
+
Guide::Photographer.new(image_name, extension).image_path
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def infer_id_from_class_name
|
64
|
+
# for example, Structures::Checkout::Page becomes :page
|
65
|
+
self.class.name.demodulize.underscore.to_sym
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Guide::Photographer
|
2
|
+
def initialize(image_name, extension = 'png')
|
3
|
+
@image_name = image_name
|
4
|
+
@extension = extension
|
5
|
+
end
|
6
|
+
|
7
|
+
def image_path
|
8
|
+
"#{asset_host}#{image_with_path_and_digest}"
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def image_with_path_and_digest
|
14
|
+
ActionController::Base.helpers.image_path "guide/#{@image_name}.#{@extension}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def asset_host
|
18
|
+
Rails.application.config.action_controller.asset_host || ""
|
19
|
+
end
|
20
|
+
end
|