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.
Files changed (83) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +22 -0
  3. data/Rakefile +18 -0
  4. data/app/assets/javascripts/guide/application.js +21723 -0
  5. data/app/assets/javascripts/guide/scenario.js +12642 -0
  6. data/app/assets/stylesheets/guide/application.css +141 -0
  7. data/app/controllers/guide/base_controller.rb +83 -0
  8. data/app/controllers/guide/nodes_controller.rb +40 -0
  9. data/app/controllers/guide/scenarios_controller.rb +44 -0
  10. data/app/helpers/guide/application_helper.rb +4 -0
  11. data/app/helpers/guide/document_helper.rb +20 -0
  12. data/app/models/guide/arborist.rb +44 -0
  13. data/app/models/guide/bouncer.rb +34 -0
  14. data/app/models/guide/cartographer.rb +49 -0
  15. data/app/models/guide/content.rb +28 -0
  16. data/app/models/guide/default_authentication_system.rb +13 -0
  17. data/app/models/guide/default_authorisation_system.rb +18 -0
  18. data/app/models/guide/diplomat.rb +60 -0
  19. data/app/models/guide/document.rb +20 -0
  20. data/app/models/guide/endpoint_stocktaker.rb +94 -0
  21. data/app/models/guide/errors.rb +2 -0
  22. data/app/models/guide/errors/base.rb +6 -0
  23. data/app/models/guide/errors/interface_violation.rb +2 -0
  24. data/app/models/guide/errors/invalid_node.rb +2 -0
  25. data/app/models/guide/errors/invalid_scenario.rb +2 -0
  26. data/app/models/guide/errors/invalid_visibility_option.rb +2 -0
  27. data/app/models/guide/errors/permission_denied.rb +2 -0
  28. data/app/models/guide/fixture.rb +15 -0
  29. data/app/models/guide/fixtures.rb +3 -0
  30. data/app/models/guide/form_object.rb +3 -0
  31. data/app/models/guide/monkey.rb +47 -0
  32. data/app/models/guide/nobilizer.rb +9 -0
  33. data/app/models/guide/node.rb +67 -0
  34. data/app/models/guide/photographer.rb +20 -0
  35. data/app/models/guide/scout.rb +59 -0
  36. data/app/models/guide/simulator.rb +26 -0
  37. data/app/models/guide/structure.rb +75 -0
  38. data/app/models/guide/view_model.rb +37 -0
  39. data/app/view_models/guide/layout_view.rb +81 -0
  40. data/app/view_models/guide/navigation_view.rb +25 -0
  41. data/app/view_models/guide/node_view.rb +59 -0
  42. data/app/view_models/guide/scenario_layout_view.rb +49 -0
  43. data/app/view_models/guide/scenario_view.rb +39 -0
  44. data/app/views/guide/_content.html.erb +17 -0
  45. data/app/views/guide/common/_category.html.erb +9 -0
  46. data/app/views/guide/common/_footer.html.erb +21 -0
  47. data/app/views/guide/common/_locale_switcher.html.erb +6 -0
  48. data/app/views/guide/common/_navigation.html.erb +12 -0
  49. data/app/views/guide/common/_navigation_node.html.erb +39 -0
  50. data/app/views/guide/common/_page_title.html.erb +8 -0
  51. data/app/views/guide/common/_search.html.erb +23 -0
  52. data/app/views/guide/common/_unsupported_browser_message.html.erb +6 -0
  53. data/app/views/guide/common/_visibility_banner.html.erb +25 -0
  54. data/app/views/guide/nodes/_document.html.erb +3 -0
  55. data/app/views/guide/nodes/_scenario.html.erb +14 -0
  56. data/app/views/guide/nodes/_scenario_list.html.erb +10 -0
  57. data/app/views/guide/nodes/_structure.html.erb +26 -0
  58. data/app/views/guide/nodes/_template_location.html.erb +7 -0
  59. data/app/views/guide/nodes/show.html.erb +7 -0
  60. data/app/views/guide/scenarios/_scenario.html.erb +25 -0
  61. data/app/views/guide/scenarios/scenario/_locale_switcher.html.erb +5 -0
  62. data/app/views/guide/scenarios/scenario/_toolbar.html.erb +39 -0
  63. data/app/views/guide/scenarios/scenario/_visibility.html.erb +10 -0
  64. data/app/views/guide/scenarios/show.html.erb +1 -0
  65. data/app/views/layouts/guide/application.html.erb +76 -0
  66. data/app/views/layouts/guide/scenario.html.erb +38 -0
  67. data/app/views/layouts/guide/scenario/default.html.erb +1 -0
  68. data/app/views/layouts/guide/scenario/default.text.erb +1 -0
  69. data/config/initializers/assets.rb +1 -0
  70. data/config/locales/models/guide/monkey.en.yml +6 -0
  71. data/config/locales/views/guide/nodes/_structure.en.yml +7 -0
  72. data/config/routes.rb +6 -0
  73. data/lib/guide.rb +12 -0
  74. data/lib/guide/authorisation_spec_helper.rb +83 -0
  75. data/lib/guide/configuration.rb +29 -0
  76. data/lib/guide/consistency_spec_helper.rb +33 -0
  77. data/lib/guide/engine.rb +9 -0
  78. data/lib/guide/version.rb +3 -0
  79. data/lib/tasks/guide_tasks.rake +4 -0
  80. metadata +207 -16
  81. data/.gitignore +0 -26
  82. data/Gemfile +0 -4
  83. 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,13 @@
1
+ class Guide::DefaultAuthenticationSystem
2
+ def user_signed_in?
3
+ false
4
+ end
5
+
6
+ def url_for_sign_in
7
+ ''
8
+ end
9
+
10
+ def url_for_sign_out
11
+ ''
12
+ end
13
+ 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,2 @@
1
+ class Guide::Errors
2
+ 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,2 @@
1
+ class Guide::Errors::InterfaceViolation < Guide::Errors::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Guide::Errors::InvalidNode < Guide::Errors::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Guide::Errors::InvalidScenario < Guide::Errors::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Guide::Errors::InvalidVisibilityOption < Guide::Errors::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Guide::Errors::PermissionDenied < Guide::Errors::Base
2
+ 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,3 @@
1
+ module Guide::Fixtures
2
+ # This file exists to help Rails autoload the fixtures directory
3
+ end
@@ -0,0 +1,3 @@
1
+ class Guide::FormObject < OpenStruct
2
+ include ActiveModel::Validations
3
+ 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,9 @@
1
+ class Guide::Nobilizer
2
+ def bestow_title(node_path)
3
+ node_path.split("/").collect(&:titleize).join(" » ").html_safe
4
+ end
5
+
6
+ def bestow_heritage(node_path)
7
+ node_path.split("/")[0...-1].collect(&:titleize).join(" » ").html_safe
8
+ end
9
+ 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