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,59 @@
|
|
1
|
+
class Guide::Scout
|
2
|
+
def initialize(starting_node)
|
3
|
+
@starting_node = starting_node
|
4
|
+
|
5
|
+
ensure_starting_node_exists
|
6
|
+
end
|
7
|
+
|
8
|
+
def visibility_along_path(node_path)
|
9
|
+
current_node = @starting_node
|
10
|
+
path_visibility = current_node.options[:visibility]
|
11
|
+
|
12
|
+
node_ids_along_path(node_path).each do |node_id|
|
13
|
+
current_node = current_node.child_nodes[node_id]
|
14
|
+
ensure_node_exists(current_node, node_id)
|
15
|
+
path_visibility = foggiest_visibility(path_visibility,
|
16
|
+
current_node.options[:visibility])
|
17
|
+
end
|
18
|
+
|
19
|
+
path_visibility
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
VISIBILITY_PRIORITY = {
|
25
|
+
nil => 0,
|
26
|
+
:unpublished => 1,
|
27
|
+
:restricted => 2,
|
28
|
+
}
|
29
|
+
private_constant :VISIBILITY_PRIORITY
|
30
|
+
|
31
|
+
def foggiest_visibility(first, second)
|
32
|
+
case
|
33
|
+
when VISIBILITY_PRIORITY[first] > VISIBILITY_PRIORITY[second]
|
34
|
+
first
|
35
|
+
when VISIBILITY_PRIORITY[first] < VISIBILITY_PRIORITY[second]
|
36
|
+
second
|
37
|
+
else
|
38
|
+
first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def node_ids_along_path(node_path)
|
43
|
+
@node_ids ||= node_path.split("/").map {|node_id| node_id.to_sym }
|
44
|
+
end
|
45
|
+
|
46
|
+
def ensure_node_exists(node, node_id)
|
47
|
+
unless node.present?
|
48
|
+
raise Guide::Errors::InvalidNode,
|
49
|
+
"I can't give you what you're looking for because a node in your path (#{node_id}) doesn't exist."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def ensure_starting_node_exists
|
54
|
+
unless @starting_node.present?
|
55
|
+
raise Guide::Errors::InvalidNode,
|
56
|
+
"I can't give you what you're looking for because the node that you told me to start from doesn't exist. This means that something is fundamentally wrong with your setup."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Guide::Simulator
|
2
|
+
def initialize(structure, bouncer)
|
3
|
+
@structure = structure
|
4
|
+
@bouncer = bouncer
|
5
|
+
end
|
6
|
+
|
7
|
+
def fetch_scenario(scenario_id)
|
8
|
+
scenario(scenario_id).tap do |scenario|
|
9
|
+
ensure_user_can_access(scenario)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def scenario(scenario_id)
|
16
|
+
@structure.scenarios[scenario_id].tap do |scenario|
|
17
|
+
raise Guide::Errors::InvalidScenario unless scenario.present?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def ensure_user_can_access(scenario)
|
22
|
+
unless @bouncer.user_can_access?(scenario)
|
23
|
+
raise Guide::Errors::PermissionDenied
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class Guide::Structure < Guide::Node
|
2
|
+
def template
|
3
|
+
partial
|
4
|
+
end
|
5
|
+
|
6
|
+
def partial
|
7
|
+
end
|
8
|
+
|
9
|
+
def cell
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def layout_css_classes
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
|
17
|
+
def formats
|
18
|
+
[:html]
|
19
|
+
end
|
20
|
+
|
21
|
+
def stylesheets
|
22
|
+
Guide.configuration.default_stylesheets_for_structures
|
23
|
+
end
|
24
|
+
|
25
|
+
def javascripts
|
26
|
+
Guide.configuration.default_javascripts_for_structures
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.scenario(id, **options, &block)
|
30
|
+
private define_method(id, block)
|
31
|
+
|
32
|
+
scenario_definitions[id] = OpenStruct.new(
|
33
|
+
:name => id.to_s.titleize,
|
34
|
+
:options => OpenStruct.new(**options),
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
class_attribute :scenario_definitions
|
39
|
+
|
40
|
+
def self.inherited(sub_class)
|
41
|
+
sub_class.scenario_definitions = {}
|
42
|
+
super(sub_class)
|
43
|
+
end
|
44
|
+
|
45
|
+
def scenarios
|
46
|
+
@scenarios ||=
|
47
|
+
scenario_definitions.each.with_object({}) do |(id, scenario_definition), scenario_hash|
|
48
|
+
scenario_hash[id] = OpenStruct.new(
|
49
|
+
:name => scenario_definition.name,
|
50
|
+
:view_model => send(id),
|
51
|
+
:options => scenario_definition.options,
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def layout_templates
|
57
|
+
{
|
58
|
+
:html => default_layout_template
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def layout_view_model
|
63
|
+
Guide::ViewModel.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def can_be_rendered?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def default_layout_template
|
73
|
+
Guide.configuration.default_layout_for_scenarios
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Guide::ViewModel < OpenStruct
|
2
|
+
def initialize(defaults = {}, overrides = {})
|
3
|
+
inappropriate_overrides = overrides.keys - defaults.keys
|
4
|
+
if inappropriate_overrides.any?
|
5
|
+
raise Guide::Errors::InterfaceViolation.new(
|
6
|
+
"You added the #{'method'.pluralize(inappropriate_overrides.size)} "\
|
7
|
+
"[#{inappropriate_overrides.join(", ")}] to the #{self.class.name} "\
|
8
|
+
"in your scenario that #{'is'.pluralize(inappropriate_overrides.size)} "\
|
9
|
+
"not included in its official declaration "\
|
10
|
+
"(maybe in the `view_model` method on your Structure)"
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
@guide_view_model_interface_methods = defaults.keys
|
15
|
+
|
16
|
+
super(defaults.merge(overrides))
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(method, *args, &block)
|
20
|
+
if respond_to?(method)
|
21
|
+
super
|
22
|
+
else
|
23
|
+
raise Guide::Errors::InterfaceViolation.new(
|
24
|
+
"You called a method '#{method}' from your template, "\
|
25
|
+
"but it does not exist on the #{self.class.name} in your Structure."
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def guide_view_model_interface_methods
|
31
|
+
@guide_view_model_interface_methods
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_ary
|
35
|
+
nil # because Cells calls ViewModel#flatten for some reason
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class Guide::LayoutView
|
2
|
+
attr_reader :active_node_visibility, :active_node_heritage, :active_node_title
|
3
|
+
|
4
|
+
def initialize(bouncer:,
|
5
|
+
diplomat:,
|
6
|
+
content_node:,
|
7
|
+
active_node:,
|
8
|
+
active_node_heritage:,
|
9
|
+
active_node_visibility:,
|
10
|
+
active_node_title:,
|
11
|
+
authentication_system:,
|
12
|
+
injected_html:)
|
13
|
+
@bouncer = bouncer
|
14
|
+
@diplomat = diplomat
|
15
|
+
@content_node = content_node
|
16
|
+
@active_node = active_node
|
17
|
+
@active_node_heritage = active_node_heritage
|
18
|
+
@active_node_visibility = active_node_visibility
|
19
|
+
@active_node_title = active_node_title
|
20
|
+
@authentication_system = authentication_system
|
21
|
+
@injected_html = injected_html
|
22
|
+
end
|
23
|
+
|
24
|
+
def active_node_name
|
25
|
+
@active_node.name
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_homepage?
|
29
|
+
@active_node == @content_node
|
30
|
+
end
|
31
|
+
|
32
|
+
def injected_html
|
33
|
+
@injected_html.html_safe
|
34
|
+
end
|
35
|
+
|
36
|
+
def paths_to_visible_renderable_nodes
|
37
|
+
cartographer.draw_paths_to_visible_renderable_nodes(starting_node: @content_node)
|
38
|
+
end
|
39
|
+
|
40
|
+
def user_is_privileged?
|
41
|
+
@bouncer.user_is_privileged?
|
42
|
+
end
|
43
|
+
|
44
|
+
def user_signed_in?
|
45
|
+
@authentication_system.user_signed_in?
|
46
|
+
end
|
47
|
+
|
48
|
+
def url_for_sign_in
|
49
|
+
@authentication_system.url_for_sign_in
|
50
|
+
end
|
51
|
+
|
52
|
+
def url_for_sign_out
|
53
|
+
@authentication_system.url_for_sign_out
|
54
|
+
end
|
55
|
+
|
56
|
+
def supported_locales
|
57
|
+
@diplomat.supported_locales
|
58
|
+
end
|
59
|
+
|
60
|
+
def show_locale_switcher?
|
61
|
+
@bouncer.user_is_privileged? && @diplomat.multiple_supported_locales?
|
62
|
+
end
|
63
|
+
|
64
|
+
def current_locale
|
65
|
+
@diplomat.current_locale
|
66
|
+
end
|
67
|
+
|
68
|
+
def locale_param
|
69
|
+
'locale'
|
70
|
+
end
|
71
|
+
|
72
|
+
def show_image_logo?
|
73
|
+
Guide.configuration.asset_path_for_logo.present?
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def cartographer
|
79
|
+
@cartographer ||= Guide::Cartographer.new(@bouncer)
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Guide::NavigationView
|
2
|
+
delegate :id, :name, :leaf_node?, :to => :@node
|
3
|
+
|
4
|
+
def initialize(bouncer:, node:, active_node:)
|
5
|
+
@bouncer = bouncer
|
6
|
+
@node = node
|
7
|
+
@active_node = active_node
|
8
|
+
end
|
9
|
+
|
10
|
+
def child_node_views
|
11
|
+
@node.child_nodes.map do |child_node_id, child_node|
|
12
|
+
self.class.new(bouncer: @bouncer,
|
13
|
+
node: child_node,
|
14
|
+
active_node: @active_node)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def visible_to_user?
|
19
|
+
@bouncer.user_can_access?(@node)
|
20
|
+
end
|
21
|
+
|
22
|
+
def active?
|
23
|
+
@node == @active_node
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Guide::NodeView
|
2
|
+
delegate :id,
|
3
|
+
:options,
|
4
|
+
:name,
|
5
|
+
:formats,
|
6
|
+
:cell,
|
7
|
+
:template,
|
8
|
+
:layout_css_classes,
|
9
|
+
:node_type,
|
10
|
+
:can_be_rendered?,
|
11
|
+
:view_model, :to => :@node
|
12
|
+
|
13
|
+
attr_reader :node_path
|
14
|
+
|
15
|
+
def initialize(node:, bouncer:, diplomat:, node_path:)
|
16
|
+
@node = node
|
17
|
+
@bouncer = bouncer
|
18
|
+
@diplomat = diplomat
|
19
|
+
@node_path = node_path
|
20
|
+
end
|
21
|
+
|
22
|
+
def visible_scenarios
|
23
|
+
@node.scenarios.select do |scenario_id, scenario|
|
24
|
+
@bouncer.user_can_access?(scenario)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def multiple_formats?
|
29
|
+
@node.formats.size > 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def uses_cells?
|
33
|
+
cell.present?
|
34
|
+
end
|
35
|
+
|
36
|
+
def template_location
|
37
|
+
template || cell
|
38
|
+
end
|
39
|
+
|
40
|
+
def user_is_privileged?
|
41
|
+
@bouncer.user_is_privileged?
|
42
|
+
end
|
43
|
+
|
44
|
+
def supported_locales
|
45
|
+
@diplomat.supported_locales
|
46
|
+
end
|
47
|
+
|
48
|
+
def show_locale_switcher?
|
49
|
+
@bouncer.user_is_privileged? && @diplomat.multiple_supported_locales?
|
50
|
+
end
|
51
|
+
|
52
|
+
def current_locale
|
53
|
+
@diplomat.current_locale
|
54
|
+
end
|
55
|
+
|
56
|
+
def locale_param
|
57
|
+
'temp_locale'
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Guide::ScenarioLayoutView
|
2
|
+
def initialize(node:, node_title:, scenario:, format:, injected_html:)
|
3
|
+
@node = node
|
4
|
+
@node_title = node_title
|
5
|
+
@scenario = scenario
|
6
|
+
@format = format
|
7
|
+
@injected_html = injected_html
|
8
|
+
end
|
9
|
+
|
10
|
+
def node_title
|
11
|
+
@node_title
|
12
|
+
end
|
13
|
+
|
14
|
+
def scenario_name
|
15
|
+
@scenario.name
|
16
|
+
end
|
17
|
+
|
18
|
+
def node_layout_template
|
19
|
+
@node.layout_templates[format] || Guide.configuration.default_layout_for_scenarios
|
20
|
+
end
|
21
|
+
|
22
|
+
def node_layout_view
|
23
|
+
@node.layout_view_model
|
24
|
+
end
|
25
|
+
|
26
|
+
def format
|
27
|
+
@format
|
28
|
+
end
|
29
|
+
|
30
|
+
def inject_stylesheets?
|
31
|
+
@format == 'html'
|
32
|
+
end
|
33
|
+
|
34
|
+
def node_stylesheets
|
35
|
+
@node.stylesheets
|
36
|
+
end
|
37
|
+
|
38
|
+
def inject_javascripts?
|
39
|
+
@format == 'html'
|
40
|
+
end
|
41
|
+
|
42
|
+
def node_javascripts
|
43
|
+
@node.javascripts
|
44
|
+
end
|
45
|
+
|
46
|
+
def injected_html
|
47
|
+
@injected_html.html_safe
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Guide::ScenarioView
|
2
|
+
def initialize(node:, scenario:, format:)
|
3
|
+
@node = node
|
4
|
+
@scenario = scenario
|
5
|
+
@format = format
|
6
|
+
end
|
7
|
+
|
8
|
+
def uses_cells?
|
9
|
+
@node.cell.present?
|
10
|
+
end
|
11
|
+
|
12
|
+
def cell
|
13
|
+
@node.cell
|
14
|
+
end
|
15
|
+
|
16
|
+
def template
|
17
|
+
@node.template || @node.partial
|
18
|
+
end
|
19
|
+
|
20
|
+
def format
|
21
|
+
@format
|
22
|
+
end
|
23
|
+
|
24
|
+
def view
|
25
|
+
@scenario.view
|
26
|
+
end
|
27
|
+
|
28
|
+
def view_model
|
29
|
+
@scenario.view_model
|
30
|
+
end
|
31
|
+
|
32
|
+
def layout_css_classes
|
33
|
+
@node.layout_css_classes || ""
|
34
|
+
end
|
35
|
+
|
36
|
+
def wrapper_classes
|
37
|
+
@scenario.options.custom_wrapper_css || ""
|
38
|
+
end
|
39
|
+
end
|