neo4j-asciidoctor-extensions 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +9 -0
  3. data/.github/workflows/ci.yml +12 -0
  4. data/.gitignore +57 -0
  5. data/.rakeTasks +7 -0
  6. data/.rubocop.yml +11 -0
  7. data/Gemfile +5 -0
  8. data/Gemfile.lock +108 -0
  9. data/LICENSE +201 -0
  10. data/README.adoc +141 -0
  11. data/Rakefile +5 -0
  12. data/lib/neo4j_asciidoctor_extensions.rb +8 -0
  13. data/lib/neo4j_asciidoctor_extensions/course_document_attributes.rb +8 -0
  14. data/lib/neo4j_asciidoctor_extensions/course_document_attributes/extension.rb +65 -0
  15. data/lib/neo4j_asciidoctor_extensions/cypher_syntax_role.rb +8 -0
  16. data/lib/neo4j_asciidoctor_extensions/cypher_syntax_role/extension.rb +33 -0
  17. data/lib/neo4j_asciidoctor_extensions/document_metadata_generator.rb +8 -0
  18. data/lib/neo4j_asciidoctor_extensions/document_metadata_generator/extension.rb +136 -0
  19. data/lib/neo4j_asciidoctor_extensions/inline_highlighter_rouge.rb +9 -0
  20. data/lib/neo4j_asciidoctor_extensions/inline_highlighter_rouge/extension.rb +62 -0
  21. data/lib/neo4j_asciidoctor_extensions/revealjs_linear_navigation.rb +8 -0
  22. data/lib/neo4j_asciidoctor_extensions/revealjs_linear_navigation/extension.rb +31 -0
  23. data/lib/neo4j_asciidoctor_extensions/revealjs_speaker_notes_aggregator.rb +8 -0
  24. data/lib/neo4j_asciidoctor_extensions/revealjs_speaker_notes_aggregator/extension.rb +50 -0
  25. data/neo4j-asciidoctor-extensions.gemspec +29 -0
  26. data/spec/.rubocop.yml +5 -0
  27. data/spec/cypher_syntax_role_spec.rb +29 -0
  28. data/spec/document_metadata_generator_spec.rb +78 -0
  29. data/spec/inline_highlighter_rouge_spec.rb +30 -0
  30. data/spec/revealjs_linear_navigation_spec.rb +52 -0
  31. data/spec/revealjs_speaker_notes_aggregator_spec.rb +33 -0
  32. data/tasks/bundler.rake +6 -0
  33. data/tasks/lint.rake +5 -0
  34. data/tasks/rspec.rake +8 -0
  35. metadata +182 -0
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob('tasks/*.rake').each { |file| load file }
4
+
5
+ task default: %w[lint spec]
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'neo4j_asciidoctor_extensions/course_document_attributes'
4
+ require_relative 'neo4j_asciidoctor_extensions/cypher_syntax_role'
5
+ require_relative 'neo4j_asciidoctor_extensions/document_metadata_generator'
6
+ require_relative 'neo4j_asciidoctor_extensions/inline_highlighter_rouge'
7
+ require_relative 'neo4j_asciidoctor_extensions/revealjs_linear_navigation'
8
+ require_relative 'neo4j_asciidoctor_extensions/revealjs_speaker_notes_aggregator'
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+ require_relative 'course_document_attributes/extension'
5
+
6
+ Asciidoctor::Extensions.register do
7
+ postprocessor Neo4j::AsciidoctorExtensions::CourseDocumentAttributesPostProcessor
8
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+
5
+ module Neo4j
6
+ # Asciidoctor extensions by Neo4j
7
+ module AsciidoctorExtensions
8
+ include Asciidoctor
9
+
10
+ # A postprocessor that adds attributes about the course.
11
+ # /!\ This extension is tightly coupled to the course publishing project and should not be used for other purposes /!\
12
+ #
13
+ class CourseDocumentAttributesPostProcessor < Extensions::Postprocessor
14
+ use_dsl
15
+
16
+ # TODO: this slug should be configurable
17
+ TESTING_SLUG_PREFIX = '_testing_'
18
+
19
+ def process(document)
20
+ if (docdir = document.attr 'docdir')
21
+ # TODO: this path should be configurable
22
+ path = File.expand_path('../build/online/asciidoctor-module-descriptor.yml', docdir)
23
+ if File.exist?(path)
24
+ require 'yaml'
25
+ module_descriptor = YAML.load_file(path)
26
+ if (document_slug = document.attr 'slug') && document.attr('stage') != 'production'
27
+ document_slug = "#{TESTING_SLUG_PREFIX}#{document_slug}"
28
+ document.set_attr 'slug', document_slug
29
+ end
30
+ set_attributes(document, document_slug, module_descriptor)
31
+ document.set_attribute 'module-name', module_descriptor['module_name']
32
+ end
33
+ end
34
+ document
35
+ end
36
+
37
+ private
38
+
39
+ def set_attributes(document, document_slug, module_descriptor)
40
+ module_descriptor['pages'].each_with_index do |page, index|
41
+ document.set_attribute "module-toc-link-#{index}", page['url']
42
+ document.set_attribute "module-toc-title-#{index}", page['title']
43
+ page_slug = page['slug']
44
+ page_slug = "#{TESTING_SLUG_PREFIX}#{page_slug}" unless document.attr('stage') == 'production'
45
+ document.set_attribute "module-toc-slug-#{index}", page_slug
46
+ document.set_attribute "module-quiz-#{index}", page['quiz']
47
+ next unless document_slug == page_slug
48
+
49
+ set_next_attributes(document, page)
50
+ document.set_attribute 'module-quiz', page['quiz']
51
+ document.set_attribute 'module-certificate', page['certificate']
52
+ end
53
+ end
54
+
55
+ def set_next_attributes(document, page)
56
+ return unless page.key?('next')
57
+
58
+ next_page_slug = page['next']['slug']
59
+ next_page_slug = "#{TESTING_SLUG_PREFIX}#{next_page_slug}" unless document.attr('stage') == 'production'
60
+ document.set_attr 'module-next-slug', next_page_slug, false
61
+ document.set_attr 'module-next-title', page['next']['title'], false
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+ require_relative 'cypher_syntax_role/extension'
5
+
6
+ Asciidoctor::Extensions.register do
7
+ tree_processor Neo4j::AsciidoctorExtensions::CypherSyntaxRoleTreeProcessor
8
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+
5
+ module Neo4j
6
+ # Asciidoctor extensions by Neo4j
7
+ module AsciidoctorExtensions
8
+ include Asciidoctor
9
+
10
+ # A tree processor that adds a role on code blocks using "cypher-syntax" as a language.
11
+ #
12
+ # Usage:
13
+ #
14
+ # [source,cypher-syntax]
15
+ # ----
16
+ # ()
17
+ # (p)
18
+ # (l)
19
+ # (n)
20
+ # ----
21
+ #
22
+ class CypherSyntaxRoleTreeProcessor < Extensions::TreeProcessor
23
+ use_dsl
24
+
25
+ def process(document)
26
+ document.find_by(context: :listing) { |block| block.attr('language') == 'cypher-syntax' }.each do |block|
27
+ block.add_role('syntax')
28
+ end
29
+ document
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+ require_relative 'document_metadata_generator/extension'
5
+
6
+ Asciidoctor::Extensions.register do
7
+ postprocessor Neo4j::AsciidoctorExtensions::DocumentMetadataGeneratorPostProcessor
8
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+
5
+ module Neo4j
6
+ # Asciidoctor extensions by Neo4j
7
+ module AsciidoctorExtensions
8
+ include Asciidoctor
9
+
10
+ # Type of value
11
+ module ValueType
12
+ # string
13
+ STRING = 1
14
+ # [string]
15
+ LIST_STRING = 2
16
+ # [<string,string>]
17
+ LIST_TUPLES = 3
18
+ end
19
+
20
+ # A postprocess that generates a metadata file (in YAML format) from a list of document attributes.
21
+ #
22
+ # Usage:
23
+ #
24
+ # # .file.adoc
25
+ # #:document-metadata-attrs-include: author,slug,parent-path,tags*,taxonomies*<>
26
+ # #:tags: intro,course
27
+ # #:taxonomies: os=linux,programming_language=java,neo4j_version=3-5;3-6
28
+ # #:slug: intro-neo4j-4-0
29
+ # #:parent_path: /intro
30
+ #
31
+ # # .file.yml
32
+ # # ---
33
+ # # slug: intro-neo4j-4-0
34
+ # # parent_path: /intro
35
+ # # author:
36
+ # # name: Michael Hunger
37
+ # # first_name: Michael
38
+ # # last_name: Hunger
39
+ # # email: michael.hunger@neotechnology.com
40
+ # # tags:
41
+ # # - intro
42
+ # # - course
43
+ # # taxonomies:
44
+ # # - key: os
45
+ # # values:
46
+ # # - linux
47
+ # # - key: programming_language
48
+ # # values:
49
+ # # - java
50
+ # # - key: neo4j_version
51
+ # # values:
52
+ # # - 3-5
53
+ # # - 3-6
54
+ class DocumentMetadataGeneratorPostProcessor < Extensions::Postprocessor
55
+ use_dsl
56
+
57
+ def process(document, output)
58
+ if (attrs_include = document.attr 'document-metadata-attrs-include')
59
+ if (outfile = document.attr 'outfile')
60
+ require 'yaml'
61
+ metadata = {}
62
+ attrs_include = attrs_include
63
+ .split(',')
64
+ .map(&:strip)
65
+ .reject(&:empty?)
66
+
67
+ attrs_include.each do |attr_include|
68
+ value_type = resolve_value_type(attr_include)
69
+ attr_name = resolve_attr_name(attr_include)
70
+ if document.attr? attr_name
71
+ attr_value = resolve_attribute_value(attr_name, document, value_type)
72
+ metadata[attr_name] = attr_value
73
+ end
74
+ end
75
+ metadata['title'] = document.doctitle
76
+ write(metadata, outfile)
77
+ end
78
+ end
79
+ output
80
+ end
81
+
82
+ private
83
+
84
+ def resolve_value_type(attr_include)
85
+ if attr_include.end_with? '*'
86
+ ValueType::LIST_STRING
87
+ elsif attr_include.end_with? '*&lt;&gt;'
88
+ ValueType::LIST_TUPLES
89
+ else
90
+ ValueType::STRING
91
+ end
92
+ end
93
+
94
+ def resolve_attr_name(attr_include)
95
+ attr_include
96
+ .gsub(/\*$/, '')
97
+ .gsub(/\*&lt;&gt;$/, '')
98
+ .gsub('-', '_')
99
+ end
100
+
101
+ def write(metadata, outfile)
102
+ outputdir = File.dirname(outfile)
103
+ filename = File.basename(outfile, File.extname(outfile))
104
+ File.open("#{outputdir}/#{filename}.yml", 'w') { |file| file.write(metadata.to_yaml) }
105
+ end
106
+
107
+ def resolve_attribute_value(attr_name, document, value_type)
108
+ if attr_name == 'author'
109
+ author = {}
110
+ author['name'] = document.attr 'author'
111
+ author['first_name'] = document.attr 'firstname'
112
+ author['last_name'] = document.attr 'lastname'
113
+ author['email'] = document.attr 'email'
114
+ author
115
+ elsif value_type == ValueType::LIST_STRING
116
+ split_values(attr_name, document)
117
+ elsif value_type == ValueType::LIST_TUPLES
118
+ split_values(attr_name, document)
119
+ .map do |tuple|
120
+ key, value = tuple.split('=')
121
+ { 'key' => key.strip, 'values' => value.strip.split(';').map(&:strip).reject(&:empty?) }
122
+ end
123
+ else
124
+ document.attr attr_name
125
+ end
126
+ end
127
+
128
+ def split_values(attr_name, document)
129
+ (document.attr attr_name)
130
+ .split(',')
131
+ .map(&:strip)
132
+ .reject(&:empty?)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+ require_relative 'inline_highlighter_rouge/extension'
5
+
6
+ Asciidoctor::Extensions.register do
7
+ inline_macro Neo4j::AsciidoctorExtensions::InlineHighlighter::MonospacedTextInlineMacro
8
+ inline_macro Neo4j::AsciidoctorExtensions::InlineHighlighter::SrcInlineMacro
9
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+ require 'rouge'
5
+
6
+ module Neo4j
7
+ # Asciidoctor extensions by Neo4j
8
+ module AsciidoctorExtensions
9
+ # Inline syntax highlighter based on Rouge.
10
+ #
11
+ module InlineHighlighter
12
+ include Asciidoctor
13
+
14
+ def self.highlight_code(lang, text, doc)
15
+ return '' if text.nil? || text.strip.empty?
16
+
17
+ lexer = Rouge::Lexer.find lang
18
+ theme = Rouge::Theme.find(doc.attr('rouge-style', 'github')).new
19
+ formatter = Rouge::Formatters::HTMLInline.new(theme)
20
+ formatter.format(lexer.lex(text))
21
+ end
22
+
23
+ # Apply syntax highlighting on monospaced text cypher:
24
+ #
25
+ # Usage:
26
+ #
27
+ # # [src-cypher]`MATCH (c:Customer {customerName: 'ABCCO'}) RETURN c.BOUGHT.productName`
28
+ #
29
+ class MonospacedTextInlineMacro < Extensions::InlineMacroProcessor
30
+ use_dsl
31
+ named :monospaced_highlighter
32
+ match %r{<code class="src-([a-z]+)">([^<]+)<\/code>}i
33
+ positional_attributes :content
34
+
35
+ def process(parent, target, attrs)
36
+ raw_text = attrs[:content]
37
+ raw_text = raw_text.gsub('&#8594;', '->') if raw_text.include? '&'
38
+ raw_text = raw_text.gsub('&#8592;', '<-') if raw_text.include? '&'
39
+ highlighted_text = InlineHighlighter.highlight_code(target, raw_text, parent.document)
40
+ create_inline_pass parent, %(<code class="rouge">#{highlighted_text}</code>), attrs
41
+ end
42
+ end
43
+
44
+ # Apply syntax highlighting on src inline macro:
45
+ #
46
+ # Usage:
47
+ #
48
+ # # src:cypher[MATCH (c:Customer {customerName: 'ABCCO'}) RETURN c.BOUGHT.productName]
49
+ #
50
+ class SrcInlineMacro < Extensions::InlineMacroProcessor
51
+ use_dsl
52
+ named :src
53
+ positional_attributes :content
54
+
55
+ def process(parent, target, attrs)
56
+ new_text = InlineHighlighter.highlight_code(target, attrs[:content], parent.document)
57
+ create_inline_pass parent, %(<code class="rouge">#{new_text}</code>), attrs
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+ require_relative 'revealjs_linear_navigation/extension'
5
+
6
+ Asciidoctor::Extensions.register do
7
+ tree_processor Neo4j::AsciidoctorExtensions::RevealJsLinearNavigationTreeProcessor
8
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+
5
+ module Neo4j
6
+ # Asciidoctor extensions by Neo4j
7
+ module AsciidoctorExtensions
8
+ include Asciidoctor
9
+
10
+ # A tree process that "flatten" a reveal.js presentation to use a linear navigation.
11
+ # By default, the reveal.js converter will use a vertical navigation for the second levels of section titles (and below).
12
+ # This extension will effectively prevent that by using only first level section titles.
13
+ #
14
+ class RevealJsLinearNavigationTreeProcessor < Extensions::TreeProcessor
15
+ use_dsl
16
+
17
+ def process(document)
18
+ if document.backend == 'revealjs'
19
+ document.find_by(context: :section) { |section| section.level > 1 }.reverse_each do |section|
20
+ section.parent.blocks.delete(section)
21
+ parent_section = section.parent
22
+ parent_section = parent_section.parent while parent_section.parent && parent_section.parent.context == :section
23
+ section.level = 1
24
+ document.blocks.insert(parent_section.index + 1, section)
25
+ end
26
+ end
27
+ document
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+ require_relative 'revealjs_speaker_notes_aggregator/extension'
5
+
6
+ Asciidoctor::Extensions.register do
7
+ tree_processor Neo4j::AsciidoctorExtensions::RevealJsSpeakerNotesAggregatorTreeProcessor
8
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
4
+
5
+ module Neo4j
6
+ # Asciidoctor extensions by Neo4j
7
+ module AsciidoctorExtensions
8
+ include Asciidoctor
9
+
10
+ # A tree processor that aggregates multiple [notes] blocks in a section (slide).
11
+ #
12
+ # Usage:
13
+ #
14
+ # == Introduction
15
+ #
16
+ # [.notes]
17
+ # --
18
+ # This is a speaker note.
19
+ # --
20
+ #
21
+ # Hello!
22
+ #
23
+ # [.notes]
24
+ # --
25
+ # This is another speaker note.
26
+ # --
27
+ #
28
+ class RevealJsSpeakerNotesAggregatorTreeProcessor < Extensions::TreeProcessor
29
+ use_dsl
30
+
31
+ def process(document)
32
+ if document.backend == 'revealjs'
33
+ document.find_by(context: :section).each do |section|
34
+ notes_blocks = section.blocks.select { |block| block.context == :open && block.roles.include?('notes') }
35
+ next if notes_blocks.empty?
36
+
37
+ agg_notes_block = Asciidoctor::Block.new(section, :open, attributes: { 'role' => 'notes' })
38
+ notes_blocks.each do |notes_block|
39
+ section.blocks.delete(notes_block)
40
+ notes_block.remove_role('notes')
41
+ agg_notes_block << notes_block
42
+ end
43
+ section.blocks << agg_notes_block
44
+ end
45
+ end
46
+ document
47
+ end
48
+ end
49
+ end
50
+ end