guide 0.0.1 → 0.5.0

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