neo4j-asciidoctor-extensions 0.0.1

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 (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