guide 0.0.1 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +22 -0
- data/README.rdoc +3 -0
- data/Rakefile +26 -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/initializers/markdown.rb +15 -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 +31 -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 +198 -12
- 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
|