lurker 0.6.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/.hound.yml +4 -78
  3. data/.jshintrc +33 -0
  4. data/.rspec +1 -2
  5. data/.rubocop.yml +0 -1
  6. data/.travis.yml +30 -19
  7. data/Gemfile +0 -33
  8. data/README.md +32 -53
  9. data/Rakefile +3 -3
  10. data/cucumber.yml +1 -2
  11. data/features/atom_persistent_within_the_same_type.feature +4 -4
  12. data/features/controller_nested_schema_scaffolding.feature +7 -10
  13. data/features/controller_schema_scaffolding.feature +1 -3
  14. data/features/dereferencing_through_inlining.feature +1 -3
  15. data/features/html_generation.feature +26 -4
  16. data/features/minitest.feature +3 -8
  17. data/features/multidomain_support.feature +6 -10
  18. data/features/multitype_request_support.feature +1 -3
  19. data/features/partials.feature +3 -8
  20. data/features/request_nested_schema_scaffolding.feature +0 -2
  21. data/features/request_schema_scaffolding.feature +0 -2
  22. data/features/schema_suffixes.feature +2 -18
  23. data/features/schema_updating_within_test_suite.feature +2 -6
  24. data/features/step_definitions/additional_cli_steps.rb +16 -12
  25. data/features/support/env.rb +50 -10
  26. data/features/test_endpoint.feature +2 -9
  27. data/gemfiles/rails_4.gemfile +14 -0
  28. data/gemfiles/rails_5.gemfile +10 -0
  29. data/gemfiles/rails_6.gemfile +10 -0
  30. data/lib/lurker.rb +9 -2
  31. data/lib/lurker/cli.rb +148 -128
  32. data/lib/lurker/endpoint.rb +11 -7
  33. data/lib/lurker/form_builder.rb +22 -31
  34. data/lib/lurker/json/concerns/validatable.rb +5 -1
  35. data/lib/lurker/json/parser.rb +1 -1
  36. data/lib/lurker/json/schema.rb +19 -6
  37. data/lib/lurker/json/{writter.rb → writer.rb} +2 -2
  38. data/lib/lurker/presenters/base_presenter.rb +10 -34
  39. data/lib/lurker/presenters/endpoint_presenter.rb +19 -8
  40. data/lib/lurker/presenters/schema_presenter.rb +6 -5
  41. data/lib/lurker/presenters/service_presenter.rb +41 -7
  42. data/lib/lurker/rendering_controller.rb +14 -7
  43. data/lib/lurker/service.rb +27 -11
  44. data/lib/lurker/spec_helper/rspec.rb +0 -4
  45. data/lib/lurker/spy.rb +3 -1
  46. data/lib/lurker/templates/documentation.md.tt +1 -0
  47. data/lib/lurker/templates/javascripts/lurker.js +133 -91
  48. data/lib/lurker/templates/layouts/_sidemenu.html.erb +2 -2
  49. data/lib/lurker/templates/layouts/application.html.erb +54 -57
  50. data/lib/lurker/templates/layouts/print.html.erb +31 -0
  51. data/lib/lurker/templates/lurker/rendering/_endpoint.html.erb +37 -0
  52. data/lib/lurker/templates/lurker/rendering/_param_form_element.html.erb +1 -1
  53. data/lib/lurker/templates/lurker/rendering/_service.html.erb +7 -0
  54. data/lib/lurker/templates/lurker/rendering/_submit_form.html.erb +77 -73
  55. data/lib/lurker/templates/lurker/rendering/all.html.erb +5 -0
  56. data/lib/lurker/templates/lurker/rendering/index.html.erb +1 -10
  57. data/lib/lurker/templates/lurker/rendering/show.html.erb +1 -37
  58. data/lib/lurker/templates/public/application.css +6 -2
  59. data/lib/lurker/templates/public/application.js +13 -13
  60. data/lib/lurker/templates/stylesheets/application.scss +3 -0
  61. data/lib/lurker/version.rb +1 -1
  62. data/lurker.gemspec +31 -33
  63. data/spec/spec_helper.rb +0 -1
  64. data/tasks/build.rake +12 -8
  65. data/tasks/generate.rake +44 -17
  66. data/templates/Dockerfile +26 -0
  67. data/templates/generate_stuff.rb +59 -26
  68. data/templates/lurker_app.rb +27 -48
  69. data/templates/rails4_ruby26_thread_error_fix.rb +20 -0
  70. metadata +149 -106
  71. checksums.yaml.gz.sig +0 -2
  72. data.tar.gz.sig +0 -3
  73. data/Appraisals +0 -20
  74. data/gemfiles/rails_32.gemfile +0 -27
  75. data/gemfiles/rails_40.gemfile +0 -27
  76. data/gemfiles/rails_41.gemfile +0 -27
  77. data/gemfiles/rails_42.gemfile +0 -27
  78. data/lib/lurker/templates/lurker/rendering/_param_form_legend.html.erb +0 -1
  79. data/lib/lurker/templates/meta_service.md.erb +0 -20
  80. data/lib/lurker/validation_error.rb +0 -4
  81. data/templates/rails32_http_patch_support.rb +0 -125
  82. metadata.gz.sig +0 -0
@@ -2,28 +2,30 @@ require 'ostruct'
2
2
  require 'abstract_controller'
3
3
  require 'action_view'
4
4
  require 'action_dispatch/http/mime_type'
5
+ require 'action_dispatch/routing'
5
6
 
6
7
  module Lurker
7
8
  class RenderingController < ::AbstractController::Base
8
9
  # Include all the concerns we need to make this work
10
+ include AbstractController::Logger
9
11
  include AbstractController::Helpers
10
12
  include AbstractController::Rendering
11
- include ActionView::Rendering if defined?(ActionView::Rendering)
12
- include ActionView::Layouts if defined?(ActionView::Layouts) # Rails 4.1.x
13
+ include AbstractController::AssetPaths
13
14
  include AbstractController::Layouts if defined?(AbstractController::Layouts) # Rails 3.2.x, 4.0.x
15
+ include ActionView::Layouts if defined?(ActionView::Layouts) # Rails 4.1.x
16
+ include ActionView::Rendering if defined?(ActionView::Rendering)
14
17
  include ActionView::Context
15
18
 
16
- self.view_paths = File.join(File.dirname(__FILE__), "templates")
19
+ attr_writer :service_presenter, :endpoint_presenter
17
20
 
18
21
  # Define additional helpers, this one is for csrf_meta_tag
19
- helper_method :protect_against_forgery?, :tag_with_anchor
22
+ helper_method :title, :tag_with_anchor, :protect_against_forgery?
20
23
 
21
24
  # override the layout in your subclass if needed.
22
25
  layout 'application'
23
26
 
24
- # we are not in a browser, no need for this
25
- def protect_against_forgery?
26
- false
27
+ def title
28
+ [@service_presenter.try(:title), @endpoint_presenter.try(:title)].compact.join ' | '
27
29
  end
28
30
 
29
31
  def tag_with_anchor(tag, content, anchor_slug = nil)
@@ -37,6 +39,11 @@ module Lurker
37
39
  EOS
38
40
  end
39
41
 
42
+ # we are not in a browser, no need for this
43
+ def protect_against_forgery?
44
+ false
45
+ end
46
+
40
47
  # so that your flash calls still work
41
48
  def flash
42
49
  {}
@@ -3,8 +3,17 @@ require 'yaml'
3
3
  # Services represent a group of Lurker API endpoints in a directory
4
4
  class Lurker::Service
5
5
  attr_reader :service_dir, :schema
6
+ attr_writer :documentation
6
7
  attr_accessor :opened_endpoints
7
8
  SUFFIX = '.service.yml'
9
+ DEFAULT_SCHEMA = {
10
+ 'name' => '',
11
+ 'basePath' => '',
12
+ 'description' => '',
13
+ 'domains' => {},
14
+ 'consumes' => %w(application/x-www-form-urlencode application/json),
15
+ 'produces' => %w(application/json)
16
+ }
8
17
 
9
18
  def self.default_service
10
19
  new(Lurker.service_path)
@@ -15,14 +24,9 @@ class Lurker::Service
15
24
  @service_dir = File.expand_path(service_dir)
16
25
  @service_filename = service_name
17
26
  @schema = if persisted? && (schema = YAML.load_file(service_path)).is_a?(Hash)
18
- Lurker::Json::Schema.new(schema)
27
+ Lurker::Json::Schema.new(schema.stringify_keys, uri: service_path)
19
28
  else
20
- Lurker::Json::Schema.new(
21
- 'name' => service_filename,
22
- 'basePath' => '',
23
- 'description' => '',
24
- 'domains' => {}
25
- )
29
+ Lurker::Json::Schema.new(DEFAULT_SCHEMA.merge('name' => service_filename), uri: service_path)
26
30
  end
27
31
  end
28
32
 
@@ -39,7 +43,7 @@ class Lurker::Service
39
43
  end
40
44
 
41
45
  def persist!
42
- Lurker::Json::Writter.write(schema, service_path) unless File.exist?(service_path)
46
+ Lurker::Json::Writer.write(schema, service_path) unless File.exist?(service_path)
43
47
  @opened_endpoints.each { |ep| ep.persist! if ep.respond_to?(:persist!) }
44
48
  end
45
49
 
@@ -97,19 +101,31 @@ class Lurker::Service
97
101
  if base_path && !base_path.end_with?('/')
98
102
  base_path + '/'
99
103
  else
100
- base_path
104
+ base_path || '/'
101
105
  end
102
106
  end
103
107
 
104
108
  def description
105
- @schema['description']
109
+ schema['description']
106
110
  end
107
111
 
108
112
  def discussion
109
- @schema['discussion']
113
+ schema['discussion']
110
114
  end
111
115
 
112
116
  def domains
113
117
  schema['domains']
114
118
  end
119
+
120
+ def request_media_types
121
+ schema['consumes']
122
+ end
123
+
124
+ def response_media_types
125
+ schema['produces']
126
+ end
127
+
128
+ def documentation
129
+ @documentation ||= schema.documentation
130
+ end
115
131
  end
@@ -16,9 +16,5 @@ if defined?(RSpec) && RSpec.respond_to?(:configure)
16
16
  end
17
17
  Lurker::Spy.on(options, &example)
18
18
  end
19
-
20
- unless RSpec::Core::Version::STRING > '3'
21
- config.treat_symbols_as_metadata_keys_with_true_values = true
22
- end
23
19
  end
24
20
  end
@@ -16,7 +16,9 @@ module Lurker
16
16
  @block = block
17
17
 
18
18
  @service = if defined?(Rails)
19
- Service.new(Rails.root.join(DEFAULT_SERVICE_PATH).to_s, Rails.application.class.parent_name)
19
+ rails_app_class = Rails.application.class
20
+ rails_app_name = rails_app_class.respond_to?(:module_parent_name) ? rails_app_class.module_parent_name : rails_app_class.parent_name
21
+ Service.new(Rails.root.join(DEFAULT_SERVICE_PATH).to_s, rails_app_name)
20
22
  else
21
23
  Service.default_service
22
24
  end
@@ -0,0 +1 @@
1
+ This is an autogenerated documentation stub. You can edit it in: `<%= config[:path] %>`
@@ -1,101 +1,143 @@
1
- var window = this.window;
2
- var $ = window.$;
3
- var Lurker = {
4
-
5
- disableSubmitButton: function() {
6
- $("#submit-api").attr("disabled", true);
7
- },
8
-
9
- enableSubmitButton: function() {
10
- $("#submit-api").attr("disabled", false);
11
- },
1
+ (function() {
2
+ var $ = window.jQuery;
3
+ var hljs = window.hljs;
4
+
5
+ var Lurker = {
6
+ disableSubmitButton: function() {
7
+ $("#submit-api").attr("disabled", true);
8
+ },
9
+
10
+ enableSubmitButton: function() {
11
+ $("#submit-api").attr("disabled", false);
12
+ },
13
+
14
+ fillInInfoTab: function($tab, xhr) {
15
+ $tab.find('.status td.value').text(xhr.status + " " + xhr.statusText);
16
+ $tab.find('#headers').text(xhr.getAllResponseHeaders());
17
+
18
+ var realTimeTaken = Lurker.lastRequest.endTime - Lurker.lastRequest.startTime;
19
+ $tab.find('.time td.value').text(realTimeTaken + " ms");
20
+ },
21
+
22
+ fillInRawTab: function($tab, xhr) {
23
+ var content;
24
+ switch (Lurker.detectContentType(xhr)) {
25
+ case "json":
26
+ var json = JSON.stringify(JSON.parse(xhr.responseText), null, 2);
27
+ content = hljs.highlightAuto(json).value;
28
+ break;
29
+ default:
30
+ content = xhr.responseText;
31
+ }
32
+ $tab.html(content);
33
+ },
34
+
35
+ detectContentType: function(response) {
36
+ var contentType = response.getResponseHeader("Content-Type");
37
+ var detectedContentType = null;
38
+
39
+ if (contentType.match(/application\/json/)) {
40
+ detectedContentType = 'json';
41
+ }
42
+
43
+ return detectedContentType;
44
+ },
45
+
46
+ buildActionUrl: function(host, template, values) {
47
+ for (var i = 0; i < values.length; i++) {
48
+ var placeholder = new RegExp(':' + values[i].label);
49
+ template = template.replace(placeholder, encodeURIComponent(values[i].value));
50
+ }
51
+ return host + template;
52
+ },
53
+
54
+ serializePayload: function(payload, contentType) {
55
+ if (contentType === 'application/json' || /\+json$/.test(contentType)) {
56
+ return JSON.stringify(payload);
57
+ } else {
58
+ // default to 'application/x-www-form-urlencoded'
59
+ return jQuery.param(payload);
60
+ }
61
+ },
62
+
63
+ generateCurlCommand: function(targetUrl, method, payload, contentType) {
64
+ var serializedPayload = Lurker.serializePayload(payload, contentType);
65
+ var results = [];
66
+ results.push('curl');
67
+ results.push('-X ' + method);
68
+ if (serializedPayload.length > 0) {
69
+ if (contentType != 'application/x-www-form-urlencoded') {
70
+ results.push("-H 'Content-Type: " + contentType + "'");
71
+ }
72
+ results.push("-d '" + serializedPayload + "'");
73
+ }
74
+ results.push("'" + targetUrl + "'");
75
+ return results.join(' ');
76
+ },
77
+
78
+ performRequest: function(host, method, template, values, payload, contentType) {
79
+ Lurker.disableSubmitButton();
80
+
81
+ $.ajax({
82
+ url: Lurker.buildActionUrl(host, template, values),
83
+ data: Lurker.serializePayload(payload, contentType),
84
+ method: method,
85
+ contentType: contentType,
86
+ processData: false
87
+ }).complete(Lurker.onComplete);
88
+
89
+ Lurker.lastRequest = {};
90
+ Lurker.lastRequest.startTime = Date.now();
91
+
92
+ return false;
93
+ },
94
+
95
+ onComplete: function(xhr) {
96
+ Lurker.lastRequest.endTime = Date.now();
97
+ Lurker.enableSubmitButton();
98
+
99
+ var $responseDiv = $("#show-api-response-div");
100
+ $responseDiv.find("[ref^='response']").hide();
101
+
102
+ Lurker.fillInInfoTab($responseDiv.showNavTab("info"), xhr);
103
+ Lurker.fillInRawTab($responseDiv.showNavTab("raw"), xhr);
104
+ }
105
+ };
12
106
 
13
- detectContentType: function(response) {
14
- var contentType = response.getResponseHeader("Content-Type");
15
- var detectedContentType = null;
107
+ window.Lurker = Lurker;
16
108
 
17
- if (contentType.match(/application\/json/)) {
18
- detectedContentType = 'json';
109
+ $(function($) {
110
+ var activeMenuItem = $('#side-menu').find('a[href="' + window.location.pathname + '"]');
111
+ if (activeMenuItem.length === 1) {
112
+ activeMenuItem.addClass('hovered')
113
+ .parents('.collapse').addClass('in')
114
+ .parents('.endpoint-group').addClass('active');
19
115
  }
20
116
 
21
- return detectedContentType;
22
- },
23
-
24
- fillInInfoTab: function($tab, xhr) {
25
- $tab.find('.status td.value').text(xhr.status + " " + xhr.statusText);
26
- $tab.find('#headers').text(xhr.getAllResponseHeaders());
27
-
28
- var realTimeTaken = Lurker.lastRequest.endTime - Lurker.lastRequest.startTime;
29
- $tab.find('.time td.value').text(realTimeTaken + " ms");
30
- },
31
-
32
- fillInRawTab: function($tab, xhr) {
33
- switch (Lurker.detectContentType(xhr)) {
34
- case "json":
35
- var json = JSON.stringify(JSON.parse(xhr.responseText), null, 2);
36
- var content = hljs.highlightAuto(json).value;
37
- break;
38
- default:
39
- var content = xhr.responseText;
117
+ window.domain = window.localStorage.lastDomain || '/';
118
+ window.domainName = window.localStorage.lastDomainName || 'Local';
119
+ function currentDomain(domain, domainName) {
120
+ $('.domains').find('.current')
121
+ .text(domainName)
122
+ .data('domain', domain);
40
123
  }
41
- $tab.html(content);
42
- },
43
-
44
- onComplete: function(xhr) {
45
- Lurker.lastRequest.endTime = Date.now();
46
- Lurker.enableSubmitButton();
47
-
48
- $("#show-api-response-div [ref^='response']").hide();
49
-
50
- Lurker.fillInInfoTab($("#show-api-response-div").showNavTab("info"), xhr);
51
- Lurker.fillInRawTab($("#show-api-response-div").showNavTab("raw"), xhr);
52
- },
53
-
54
- onSubmit: function($form) {
55
- Lurker.disableSubmitButton();
56
-
57
- $.ajax({
58
- url: $form.attr('action'),
59
- method: $form.attr('method'),
60
- data: $form.serialize()
61
- }).complete(Lurker.onComplete);
62
-
63
- Lurker.lastRequest = {};
64
- Lurker.lastRequest.startTime = Date.now();
65
-
66
- return false;
67
- }
68
- };
69
-
70
- $(function($) {
71
- var activeMenuItem = $('#side-menu a[href="' + window.location.pathname + '"]');
72
- if (activeMenuItem.length === 1) {
73
- activeMenuItem.addClass('hovered').parents('.collapse').addClass('in').parents('.endpoint-group').addClass('active');
74
- }
75
-
76
- window.domain = window.localStorage.lastDomain || '/';
77
- window.domainName = window.localStorage.lastDomainName || 'Local';
78
- function currentDomain(domain, domainName) {
79
- $('.domains').find('.current')
80
- .text(domainName)
81
- .data('domain', domain);
82
- }
83
-
84
- $('.domains .domain').click(function() {
85
- window.domain = window.localStorage.lastDomain = $(this).data('domain');
86
- window.domainName = window.localStorage.lastDomainName = $(this).text() + " (" + window.domain + ")";
124
+
125
+ $('.domains .domain').click(function() {
126
+ window.domain = window.localStorage.lastDomain = $(this).data('domain');
127
+ window.domainName = window.localStorage.lastDomainName = $(this).text() + " (" + window.domain + ")";
128
+ currentDomain(window.domain, window.domainName);
129
+ });
130
+
131
+ $('.domains .current.btn').click(function() {
132
+ window.location = $(this).data('domain');
133
+ });
87
134
  currentDomain(window.domain, window.domainName);
88
135
  });
89
136
 
90
- $('.domains .current.btn').click(function() {
91
- window.location = $(this).data('domain');
137
+ $.fn.extend({
138
+ showNavTab: function(name) {
139
+ return $(this).find("[ref=response-" + name + "]").show();
140
+ }
92
141
  });
93
- currentDomain(window.domain, window.domainName);
94
- });
95
-
96
- $.fn.extend({
97
- showNavTab: function(name) {
98
- return $(this).find("[ref=response-" + name + "]").show();
99
- },
100
- });
142
+ })();
101
143
 
@@ -5,9 +5,9 @@
5
5
  <% endpoints.each do |endpoint| %>
6
6
  <li class="<%= (@endpoint_presenter.nil? && 'opened') || (endpoint.url == @endpoint_presenter.url && 'active') %>">
7
7
  <a href="<%= endpoint.url %>">
8
- <span class="btn btn-<%= endpoint.verb_colorname %> btn-xs"><%= endpoint.verb %>&nbsp;<%= endpoint.named_path %></span>
9
- <br>
10
8
  <span><%= endpoint.title %></span>
9
+ <br>
10
+ <span class="btn btn-<%= endpoint.verb_colorname %> btn-xs"><%= endpoint.verb %>&nbsp;<%= endpoint.named_path %></span>
11
11
  </a>
12
12
  </li>
13
13
  <% end %>
@@ -1,70 +1,67 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
-
4
3
  <head>
5
-
6
- <meta charset="utf-8">
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
-
9
- <title><%= @service_presenter.title %></title>
10
- <link href="<%= @service_presenter.html_directory %>/application-<%= @service_presenter.options['css'] %>.css" media="all" rel="stylesheet">
11
- <style type="text/css">
12
- @font-face {
13
- font-family: 'FontAwesome';
14
- src: url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.eot?v=4.0.3");
15
- src: url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.eot?#iefix&v=4.0.3") format("embedded-opentype"),
16
- url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.woff?v=4.0.3") format("woff"),
17
- url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.ttf?v=4.0.3") format("truetype"),
18
- url("<%= @service_presenter.html_directory %>/fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular") format("svg");
19
- font-weight: normal;
20
- font-style: normal;
21
- }
22
- </style>
23
-
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= title %></title>
7
+ <link href="<%= @service_presenter.asset_path('application.css') %>" media="all" rel="stylesheet">
8
+ <style type="text/css">
9
+ @font-face {
10
+ font-family: 'FontAwesome';
11
+ src: url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.eot?v=4.0.3' %>");
12
+ src: url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.eot?#iefix&v=4.0.3' %>") format("embedded-opentype"),
13
+ url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.woff?v=4.0.3' %>") format("woff"),
14
+ url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.ttf?v=4.0.3' %>") format("truetype"),
15
+ url("<%= @service_presenter.asset_path '/fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular' %>") format("svg");
16
+ font-weight: normal;
17
+ font-style: normal;
18
+ }
19
+ </style>
24
20
  </head>
25
-
26
21
  <body>
27
22
 
28
- <header class="navbar navbar-static-top bs-docs-nav" id="top" role="banner">
29
- <div class="container">
30
- <div class="navbar-header">
31
- <a class="navbar-brand" href="<%= @service_presenter.html_directory %>"><%= @service_presenter.name %></a>
32
- </div>
33
- </div>
34
- </header>
23
+ <header class="navbar navbar-static-top bs-docs-nav" id="top" role="banner">
24
+ <div class="container">
25
+ <div class="navbar-header">
26
+ <a class="navbar-brand" href="<%= @service_presenter.html_directory %>"><%= @service_presenter.name %></a>
27
+ </div>
28
+ </div>
29
+ </header>
35
30
 
36
- <div class="container bs-docs-container">
37
- <div class="row">
38
- <div class="col-md-9" role="main">
39
- <div class="bs-docs-section">
40
- <%= yield %>
41
- </div>
42
- </div>
43
- <div class="col-md-3">
44
- <div class="bs-docs-sidebar hidden-print" role="complementary">
45
- <ul class="nav bs-docs-sidenav">
46
- <%= render 'layouts/sidemenu' %>
47
- </ul>
48
- </div>
49
- </div>
31
+ <div class="container bs-docs-container">
32
+ <div class="row">
33
+ <div class="col-md-9" role="main">
34
+ <div class="bs-docs-section">
35
+ <%= yield %>
50
36
  </div>
51
37
  </div>
52
-
53
- <footer class="bs-docs-footer" role="contentinfo">
54
- <div class="container">
55
- <ul class="bs-docs-footer-links muted">
56
- <li>·</li>
57
- <% if @service_presenter.options[:footer].present? %>
58
- <li><%= @service_presenter.name %>&nbsp;<%= @service_presenter.options[:footer] %></li>
59
- <li>·</li>
60
- <% end %>
61
- <% if @service_presenter.options[:lurker].present? %>
62
- <li><%= @service_presenter.options[:lurker] %></li>
63
- <li>·</li>
64
- <% end %>
38
+ <div class="col-md-3">
39
+ <div class="bs-docs-sidebar hidden-print" role="complementary">
40
+ <ul class="nav bs-docs-sidenav">
41
+ <%= render 'layouts/sidemenu' %>
65
42
  </ul>
66
43
  </div>
67
- </footer>
68
- <script src="<%= @service_presenter.html_directory %>/application-<%= @service_presenter.options['js'] %>.js"></script>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <footer class="bs-docs-footer" role="contentinfo">
49
+ <div class="container">
50
+ <ul class="bs-docs-footer-links muted">
51
+ <li>·</li>
52
+ <% if @service_presenter.footer.present? %>
53
+ <li><%= raw @service_presenter.footer %></li>
54
+ <li>·</li>
55
+ <% end %>
56
+ <% if @service_presenter.lurker.present? %>
57
+ <li><%= raw @service_presenter.lurker %></li>
58
+ <li>·</li>
59
+ <% end %>
60
+ </ul>
61
+ </div>
62
+ </footer>
63
+
64
+ <script src="<%= @service_presenter.asset_path 'application.js' %>"></script>
65
+
69
66
  </body>
70
67
  </html>