nexmo_markdown_renderer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +54 -0
  4. data/config/code_languages.yml +138 -0
  5. data/config/dynamic_content.yml +1 -0
  6. data/config/locales/en.yml +176 -0
  7. data/lib/nexmo_markdown_renderer.rb +28 -0
  8. data/lib/nexmo_markdown_renderer/cli.rb +13 -0
  9. data/lib/nexmo_markdown_renderer/core_ext/html.rb +7 -0
  10. data/lib/nexmo_markdown_renderer/core_ext/string.rb +14 -0
  11. data/lib/nexmo_markdown_renderer/filters/anchor_filter.rb +14 -0
  12. data/lib/nexmo_markdown_renderer/filters/audio_filter.rb +18 -0
  13. data/lib/nexmo_markdown_renderer/filters/block_escape_filter.rb +21 -0
  14. data/lib/nexmo_markdown_renderer/filters/break_filter.rb +10 -0
  15. data/lib/nexmo_markdown_renderer/filters/code_filter.rb +62 -0
  16. data/lib/nexmo_markdown_renderer/filters/code_snippet_filter.rb +187 -0
  17. data/lib/nexmo_markdown_renderer/filters/code_snippet_list_filter.rb +26 -0
  18. data/lib/nexmo_markdown_renderer/filters/code_snippets_filter.rb +170 -0
  19. data/lib/nexmo_markdown_renderer/filters/collapsible_filter.rb +27 -0
  20. data/lib/nexmo_markdown_renderer/filters/columns_filter.rb +47 -0
  21. data/lib/nexmo_markdown_renderer/filters/concept_list_filter.rb +30 -0
  22. data/lib/nexmo_markdown_renderer/filters/dynamic_content_filter.rb +28 -0
  23. data/lib/nexmo_markdown_renderer/filters/external_link_filter.rb +29 -0
  24. data/lib/nexmo_markdown_renderer/filters/frontmatter_filter.rb +11 -0
  25. data/lib/nexmo_markdown_renderer/filters/heading_filter.rb +57 -0
  26. data/lib/nexmo_markdown_renderer/filters/i18n/frontmatter_filter.rb +16 -0
  27. data/lib/nexmo_markdown_renderer/filters/i18n/smartling_converter_filter.rb +22 -0
  28. data/lib/nexmo_markdown_renderer/filters/icon_filter.rb +19 -0
  29. data/lib/nexmo_markdown_renderer/filters/indent_filter.rb +17 -0
  30. data/lib/nexmo_markdown_renderer/filters/inline_escape_filter.rb +14 -0
  31. data/lib/nexmo_markdown_renderer/filters/js_sequence_diagram_filter.rb +18 -0
  32. data/lib/nexmo_markdown_renderer/filters/label_filter.rb +29 -0
  33. data/lib/nexmo_markdown_renderer/filters/language_filter.rb +12 -0
  34. data/lib/nexmo_markdown_renderer/filters/markdown_filter.rb +81 -0
  35. data/lib/nexmo_markdown_renderer/filters/mermaid_filter.rb +29 -0
  36. data/lib/nexmo_markdown_renderer/filters/modal_filter.rb +37 -0
  37. data/lib/nexmo_markdown_renderer/filters/partial_filter.rb +29 -0
  38. data/lib/nexmo_markdown_renderer/filters/php_inliner_filter.rb +11 -0
  39. data/lib/nexmo_markdown_renderer/filters/screenshot_filter.rb +22 -0
  40. data/lib/nexmo_markdown_renderer/filters/tab_filter.rb +298 -0
  41. data/lib/nexmo_markdown_renderer/filters/techio_filter.rb +20 -0
  42. data/lib/nexmo_markdown_renderer/filters/tooltip_filter.rb +18 -0
  43. data/lib/nexmo_markdown_renderer/filters/unfreeze_filter.rb +16 -0
  44. data/lib/nexmo_markdown_renderer/filters/use_case_list_filter.rb +20 -0
  45. data/lib/nexmo_markdown_renderer/initializers/doc_finder.rb +5 -0
  46. data/lib/nexmo_markdown_renderer/initializers/i18n.rb +4 -0
  47. data/lib/nexmo_markdown_renderer/initializers/redcarpet.rb +7 -0
  48. data/lib/nexmo_markdown_renderer/markdown_renderer.rb +47 -0
  49. data/lib/nexmo_markdown_renderer/models/code_language.rb +79 -0
  50. data/lib/nexmo_markdown_renderer/models/code_snippet.rb +72 -0
  51. data/lib/nexmo_markdown_renderer/models/concept.rb +83 -0
  52. data/lib/nexmo_markdown_renderer/models/tutorial.rb +148 -0
  53. data/lib/nexmo_markdown_renderer/models/use_case.rb +81 -0
  54. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/android.rb +25 -0
  55. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/base.rb +12 -0
  56. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/curl.rb +29 -0
  57. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/dotnet.rb +23 -0
  58. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/java.rb +32 -0
  59. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/javascript.rb +23 -0
  60. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/kotlin.rb +25 -0
  61. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/objective_c.rb +25 -0
  62. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/php.rb +23 -0
  63. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/python.rb +23 -0
  64. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/ruby.rb +23 -0
  65. data/lib/nexmo_markdown_renderer/services/code_snippet_renderer/swift.rb +25 -0
  66. data/lib/nexmo_markdown_renderer/services/doc_finder.rb +119 -0
  67. data/lib/nexmo_markdown_renderer/views/code_snippets/_application_messages_dispatch.html.erb +9 -0
  68. data/lib/nexmo_markdown_renderer/views/code_snippets/_application_rtc.html.erb +28 -0
  69. data/lib/nexmo_markdown_renderer/views/code_snippets/_application_voice.html.erb +24 -0
  70. data/lib/nexmo_markdown_renderer/views/code_snippets/_code_only.html.erb +6 -0
  71. data/lib/nexmo_markdown_renderer/views/code_snippets/_configure_client.html.erb +20 -0
  72. data/lib/nexmo_markdown_renderer/views/code_snippets/_dependencies.html.erb +11 -0
  73. data/lib/nexmo_markdown_renderer/views/code_snippets/_write_code.html.erb +13 -0
  74. data/lib/nexmo_markdown_renderer/views/code_snippets/list/plain.html.erb +10 -0
  75. data/lib/nexmo_markdown_renderer/views/concepts/list/plain.html.erb +5 -0
  76. data/lib/nexmo_markdown_renderer/views/use_case/_index.html.erb +41 -0
  77. data/lib/nexmo_markdown_renderer/views/use_case/index.html.erb +48 -0
  78. data/lib/nexmo_markdown_renderer/views/use_case/list/plain.html.erb +5 -0
  79. data/lib/nexmo_markdown_renderer/views/use_case/show.html.erb +8 -0
  80. data/lib/version.rb +7 -0
  81. metadata +322 -0
@@ -0,0 +1,18 @@
1
+ module Nexmo
2
+ module Markdown
3
+ class TooltipFilter < Banzai::Filter
4
+ def call(input)
5
+ input.gsub(/\^\[([a-zA-Z0-9\s:\-]+)\]\((.+?)\)/) do
6
+ tooltip = <<~HEREDOC
7
+ <span class="Vlt-tooltip Vlt-tooltip--top" title="#{$2}" tabindex="0">
8
+ #{$1}&nbsp;
9
+ <svg class="Vlt-icon Vlt-icon--smaller Vlt-icon--text-bottom Vlt-blue" aria-hidden="true"><use xlink:href="/symbol/volta-icons.svg#Vlt-icon-help-negative"/></svg>
10
+ </span>
11
+ HEREDOC
12
+
13
+ "FREEZESTART#{Base64.urlsafe_encode64(tooltip)}FREEZEEND"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module Nexmo
2
+ module Markdown
3
+ class UnfreezeFilter < Banzai::Filter
4
+ def call(input)
5
+ input.gsub!('FREEZESTARTFREEZEEND', '')
6
+ input.gsub!('<p>FREEZESTART', 'FREEZESTART')
7
+ input.gsub!('FREEZEEND</p>', 'FREEZEEND')
8
+
9
+ input.gsub!(/FREEZESTART(.+?)FREEZEEND/m) do |_s|
10
+ Base64.urlsafe_decode64($1).force_encoding(Encoding::UTF_8)
11
+ end
12
+ input
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module Nexmo
2
+ module Markdown
3
+ class UseCaseListFilter < Banzai::Filter
4
+ def call(input)
5
+ input.gsub(/```use_cases(.+?)```/m) do |_s|
6
+ config = YAML.safe_load($1)
7
+ @product = config['product']
8
+ @use_cases = Nexmo::Markdown::UseCase.by_product(@product)
9
+
10
+ # Default to plain layout, but allow people to override it
11
+ config['layout'] = 'list/plain' unless config['layout']
12
+
13
+ erb = File.read("#{GEM_ROOT}/lib/nexmo_markdown_renderer/views/use_case/#{config['layout']}.html.erb")
14
+ html = ERB.new(erb).result(binding)
15
+ "FREEZESTART#{Base64.urlsafe_encode64(html)}FREEZEEND"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ Nexmo::Markdown::DocFinder.configure do |config|
2
+ config.paths << "#{ENV['DOCS_BASE_PATH']}/_documentation"
3
+ config.paths << "#{ENV['DOCS_BASE_PATH']}/_use_cases"
4
+ config.paths << "#{ENV['DOCS_BASE_PATH']}/_tutorials"
5
+ end
@@ -0,0 +1,4 @@
1
+ require 'i18n'
2
+
3
+ I18n.load_path += Dir[File.join("#{GEM_ROOT}/config/locales".freeze, '*.yml'.freeze)]
4
+ I18n.default_locale = :en
@@ -0,0 +1,7 @@
1
+ require 'redcarpet'
2
+ require 'rouge'
3
+ require 'rouge/plugins/redcarpet'
4
+
5
+ class HTML < Redcarpet::Render::HTML
6
+ include Rouge::Plugins::Redcarpet
7
+ end
@@ -0,0 +1,47 @@
1
+ module Nexmo
2
+ module Markdown
3
+ class Renderer < Banzai::Pipeline
4
+ def initialize(options = {})
5
+ super(
6
+ # As Markdown
7
+ FrontmatterFilter,
8
+ PhpInlinerFilter,
9
+ InlineEscapeFilter,
10
+ BlockEscapeFilter,
11
+ ScreenshotFilter,
12
+ AnchorFilter,
13
+ AudioFilter,
14
+ DynamicContentFilter,
15
+ TooltipFilter,
16
+ CollapsibleFilter,
17
+ TabFilter.new(options),
18
+ CodeSnippetsFilter.new(options),
19
+ CodeSnippetFilter.new(options),
20
+ CodeFilter,
21
+ IndentFilter,
22
+ ModalFilter,
23
+ JsSequenceDiagramFilter,
24
+ MermaidFilter,
25
+ PartialFilter.new(options),
26
+ TechioFilter,
27
+ UseCaseListFilter,
28
+ CodeSnippetListFilter,
29
+ ConceptListFilter.new(options),
30
+ LanguageFilter,
31
+ ColumnsFilter,
32
+ MarkdownFilter.new(options),
33
+
34
+ # As HTML
35
+ HeadingFilter,
36
+ LabelFilter.new(options),
37
+ BreakFilter,
38
+ UnfreezeFilter,
39
+ IconFilter,
40
+ ExternalLinkFilter
41
+ )
42
+ end
43
+
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,79 @@
1
+ module Nexmo
2
+ module Markdown
3
+ class CodeLanguage
4
+ include ActiveModel::Model
5
+ attr_accessor :key, :label, :type, :dependencies, :unindent, :icon, :run_command
6
+ attr_writer :weight, :linkable, :languages, :lexer
7
+
8
+ def weight
9
+ @weight || 999
10
+ end
11
+
12
+ def linkable?
13
+ return true if @linkable.nil?
14
+ @linkable
15
+ end
16
+
17
+ def lexer
18
+ return Rouge::Lexers::PHP.new({ start_inline: true }) if @lexer == 'php'
19
+ Rouge::Lexer.find(@lexer) || Rouge::Lexer.find('text')
20
+ end
21
+
22
+ def languages
23
+ @languages ||= []
24
+ @languages.map do |language|
25
+ self.class.find(language)
26
+ end
27
+ end
28
+
29
+ def self.languages
30
+ where_type('languages')
31
+ end
32
+
33
+ def self.frameworks
34
+ where_type('platforms')
35
+ end
36
+
37
+ def self.terminal_programs
38
+ where_type('terminal_programs')
39
+ end
40
+
41
+ def self.data
42
+ where_type('data')
43
+ end
44
+
45
+ def self.all
46
+ languages + frameworks + terminal_programs + data
47
+ end
48
+
49
+ def self.exists?(key)
50
+ all.detect { |lang| lang.key == key }
51
+ end
52
+
53
+ def self.find(key)
54
+ raise 'Key is missing' unless key
55
+ code_language = all.detect { |lang| lang.key == key }
56
+ raise "Language #{key} does not exist." unless code_language
57
+ code_language
58
+ end
59
+
60
+ def self.linkable
61
+ all.select(&:linkable?)
62
+ end
63
+
64
+ def self.route_constraint
65
+ { code_language: Regexp.new(linkable.map(&:key).compact.join('|')) }
66
+ end
67
+
68
+ private_class_method def self.where_type(type)
69
+ config[type].map do |key, attributes|
70
+ new(attributes.merge({ key: key, type: type }))
71
+ end
72
+ end
73
+
74
+ private_class_method def self.config
75
+ @config ||= YAML.load_file('./config/code_languages.yml')
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,72 @@
1
+ module Nexmo
2
+ module Markdown
3
+ class CodeSnippet
4
+ include ActiveModel::Model
5
+ attr_accessor :title, :product, :category, :navigation_weight, :document_path, :url
6
+
7
+ def self.by_product(product)
8
+ all.select do |block|
9
+ block.product == product
10
+ end
11
+ end
12
+
13
+ def self.all
14
+ blocks = files.map do |document_path|
15
+ document = File.read(document_path)
16
+ product = extract_product(document_path)
17
+
18
+ frontmatter = YAML.safe_load(document)
19
+
20
+ Nexmo::Markdown::CodeSnippet.new({
21
+ title: frontmatter['title'],
22
+ navigation_weight: frontmatter['navigation_weight'] || 999,
23
+ product: product,
24
+ document_path: document_path,
25
+ category: extract_category(document_path),
26
+ url: generate_url(document_path),
27
+ })
28
+ end
29
+
30
+ blocks.sort_by(&:navigation_weight)
31
+ end
32
+
33
+ def self.generate_url(path)
34
+ '/' + path.gsub(%r{#{origin}/\w{2}/}, '').gsub('.md', '')
35
+ end
36
+
37
+ def self.extract_product(path)
38
+ # Remove the prefix
39
+ path = path.gsub!(%r{#{origin}/\w{2}/}, '')
40
+
41
+ # Each file is in the form code-snippets/<title>.md, so let's remove everything after code-snippets
42
+ path = path.gsub(%r{/code-snippets/.*}, '')
43
+
44
+ path
45
+ end
46
+
47
+ def self.extract_category(path)
48
+ # Remove the prefix
49
+ path = path.gsub(%r{#{origin}/\w{2}/}, '')
50
+
51
+ # Each file is in the form code-snippets/<title>.md, so let's capture everything after code-snippets
52
+ path = path.gsub(%r{.*/code-snippets/(.*)$}, '\1')
53
+
54
+ parts = path.split('/')
55
+ parts = parts[0...-1]
56
+
57
+ return nil if parts.empty?
58
+
59
+ parts.join('/').tr('-', ' ').humanize
60
+ end
61
+
62
+ def self.files
63
+ Dir.glob("#{origin}/**/code-snippets/**/*.md")
64
+ end
65
+
66
+ def self.origin
67
+ "#{ENV['DOCS_BASE_PATH']}/_documentation"
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,83 @@
1
+ module Nexmo
2
+ module Markdown
3
+ class Concept
4
+ include ActiveModel::Model
5
+
6
+ ORIGIN = "#{ENV['DOCS_BASE_PATH']}/_documentation".freeze
7
+
8
+ FILES = [
9
+ Dir.glob("#{ORIGIN}/#{::I18n.default_locale}/**/guides/**/*.md"),
10
+ Dir.glob("#{ORIGIN}/#{::I18n.default_locale}/**/concepts/**/*.md"),
11
+ ].flatten
12
+
13
+ attr_accessor :title, :product, :description, :navigation_weight, :document_path, :url, :ignore_in_list
14
+
15
+ def self.by_name(names, language)
16
+ matches = all(language).select do |block|
17
+ concept = "#{block.product}/#{block.filename}"
18
+ match = names.include?(concept)
19
+ names.delete(concept) if match
20
+ match
21
+ end
22
+
23
+ raise "Could not find concepts: #{names.join(', ')}" unless names.empty?
24
+ matches
25
+ end
26
+
27
+ def self.by_product(product, language)
28
+ all(language).select do |block|
29
+ block.product == product
30
+ end
31
+ end
32
+
33
+ def filename
34
+ Pathname(document_path).basename.to_s.gsub('.md', '')
35
+ end
36
+
37
+ def self.all(language)
38
+ blocks = files(language).map do |document_path|
39
+ document = File.read(document_path)
40
+ product = extract_product(document_path)
41
+
42
+ frontmatter = YAML.safe_load(document)
43
+
44
+ Nexmo::Markdown::Concept.new({
45
+ title: frontmatter['title'],
46
+ description: frontmatter['description'],
47
+ navigation_weight: frontmatter['navigation_weight'] || 999,
48
+ ignore_in_list: frontmatter['ignore_in_list'],
49
+ product: product,
50
+ document_path: document_path,
51
+ url: generate_url(document_path, language),
52
+ })
53
+ end
54
+
55
+ blocks.sort_by(&:navigation_weight)
56
+ end
57
+
58
+ def self.generate_url(path, language)
59
+ '/' + path.gsub("#{ORIGIN}/#{language}/", '').gsub('.md', '')
60
+ end
61
+
62
+ def self.extract_product(path)
63
+ # Remove the prefix
64
+ path = path.gsub!(%r{#{ORIGIN}\/[a-z]{2}\/}, '')
65
+
66
+ # Each file is in the form guides/<title>.md, so let's remove the last two segments
67
+ parts = path.split('/')
68
+ parts = parts[0...-2]
69
+
70
+ # What's left once we remove the start and end of the path is our product name. This could be any number
71
+ # of parts, but it's generally 1-2
72
+ parts.join('/')
73
+ end
74
+
75
+ def self.files(language)
76
+ FILES.each_with_object([]) do |file, array|
77
+ document = file.gsub("#{ORIGIN}/#{::I18n.default_locale}/", '')
78
+ array << Nexmo::Markdown::DocFinder.find(root: ORIGIN, document: document, language: language)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,148 @@
1
+ module Nexmo
2
+ module Markdown
3
+ class Tutorial
4
+ include ActiveModel::Model
5
+ attr_accessor :raw, :name, :current_step, :current_product, :title, :description, :products, :subtasks, :prerequisites
6
+
7
+ def content_for(step_name)
8
+ if ['introduction', 'conclusion'].include? step_name
9
+ raise "Invalid step: #{step_name}" unless raw[step_name]
10
+
11
+ return raw[step_name]['content']
12
+ end
13
+
14
+ path = Nexmo::Markdown::DocFinder.find(
15
+ root: self.class.task_content_path,
16
+ document: step_name,
17
+ language: ::I18n.locale
18
+ )
19
+
20
+ File.read(path)
21
+ end
22
+
23
+ def first_step
24
+ subtasks.first['path']
25
+ end
26
+
27
+ def prerequisite?
28
+ prerequisites.pluck('path').include?(@current_step)
29
+ end
30
+
31
+ def next_step
32
+ current_task_index = subtasks.pluck('path').index(@current_step)
33
+ return nil unless current_task_index
34
+
35
+ subtasks[current_task_index + 1]
36
+ end
37
+
38
+ def previous_step
39
+ current_task_index = subtasks.pluck('path').index(@current_step)
40
+ return nil unless current_task_index
41
+ return nil if current_task_index <= 0
42
+
43
+ subtasks[current_task_index - 1]
44
+ end
45
+
46
+ def self.load(name, current_step, current_product = nil)
47
+ document_path = Nexmo::Markdown::DocFinder.find(
48
+ root: 'config/tutorials',
49
+ document: name,
50
+ language: ::I18n.default_locale,
51
+ format: 'yml'
52
+ )
53
+ config = YAML.safe_load(File.read(document_path))
54
+ current_product ||= config['products'].first
55
+
56
+ Nexmo::Markdown::Tutorial.new({
57
+ raw: config,
58
+ name: name,
59
+ current_step: current_step,
60
+ current_product: current_product,
61
+ title: config['title'],
62
+ description: config['description'],
63
+ products: config['products'],
64
+ prerequisites: load_prerequisites(config['prerequisites'], current_step),
65
+ subtasks: load_subtasks(config['introduction'], config['prerequisites'], config['tasks'], config['conclusion'], current_step),
66
+ })
67
+ end
68
+
69
+ def self.load_prerequisites(prerequisites, current_step)
70
+ return [] unless prerequisites
71
+
72
+ prerequisites.map do |t|
73
+ t_path = Nexmo::Markdown::DocFinder.find(
74
+ root: task_content_path,
75
+ document: t,
76
+ language: ::I18n.locale
77
+ )
78
+ raise "Prerequisite not found: #{t}" unless File.exist? t_path
79
+
80
+ content = File.read(t_path)
81
+ prereq = YAML.safe_load(content)
82
+ {
83
+ 'path' => t,
84
+ 'title' => prereq['title'],
85
+ 'description' => prereq['description'],
86
+ 'is_active' => t == current_step,
87
+ 'content' => content,
88
+ }
89
+ end
90
+ end
91
+
92
+ def self.load_subtasks(introduction, prerequisites, tasks, conclusion, current_step)
93
+ tasks ||= []
94
+
95
+ tasks = tasks.map do |t|
96
+ t_path = Nexmo::Markdown::DocFinder.find(
97
+ root: task_content_path,
98
+ document: t,
99
+ language: ::I18n.locale
100
+ )
101
+ raise "Subtask not found: #{t}" unless File.exist? t_path
102
+
103
+ subtask_config = YAML.safe_load(File.read(t_path))
104
+ {
105
+ 'path' => t,
106
+ 'title' => subtask_config['title'],
107
+ 'description' => subtask_config['description'],
108
+ 'is_active' => t == current_step,
109
+ }
110
+ end
111
+
112
+ if prerequisites
113
+ tasks.unshift({
114
+ 'path' => 'prerequisites',
115
+ 'title' => 'Prerequisites',
116
+ 'description' => 'Everything you need to complete this task',
117
+ 'is_active' => current_step == 'prerequisites',
118
+ })
119
+ end
120
+
121
+ if introduction
122
+ tasks.unshift({
123
+ 'path' => 'introduction',
124
+ 'title' => introduction['title'],
125
+ 'description' => introduction['description'],
126
+ 'is_active' => current_step == 'introduction',
127
+ })
128
+ end
129
+
130
+ if conclusion
131
+ tasks.push({
132
+ 'path' => 'conclusion',
133
+ 'title' => conclusion['title'],
134
+ 'description' => conclusion['description'],
135
+ 'is_active' => current_step == 'conclusion',
136
+ })
137
+ end
138
+
139
+ tasks
140
+ end
141
+
142
+ def self.task_content_path
143
+ "#{ENV['DOCS_BASE_PATH']}/_tutorials"
144
+ end
145
+ end
146
+
147
+ end
148
+ end