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.
- checksums.yaml +7 -0
- data/.editorconfig +9 -0
- data/.github/workflows/ci.yml +12 -0
- data/.gitignore +57 -0
- data/.rakeTasks +7 -0
- data/.rubocop.yml +11 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +108 -0
- data/LICENSE +201 -0
- data/README.adoc +141 -0
- data/Rakefile +5 -0
- data/lib/neo4j_asciidoctor_extensions.rb +8 -0
- data/lib/neo4j_asciidoctor_extensions/course_document_attributes.rb +8 -0
- data/lib/neo4j_asciidoctor_extensions/course_document_attributes/extension.rb +65 -0
- data/lib/neo4j_asciidoctor_extensions/cypher_syntax_role.rb +8 -0
- data/lib/neo4j_asciidoctor_extensions/cypher_syntax_role/extension.rb +33 -0
- data/lib/neo4j_asciidoctor_extensions/document_metadata_generator.rb +8 -0
- data/lib/neo4j_asciidoctor_extensions/document_metadata_generator/extension.rb +136 -0
- data/lib/neo4j_asciidoctor_extensions/inline_highlighter_rouge.rb +9 -0
- data/lib/neo4j_asciidoctor_extensions/inline_highlighter_rouge/extension.rb +62 -0
- data/lib/neo4j_asciidoctor_extensions/revealjs_linear_navigation.rb +8 -0
- data/lib/neo4j_asciidoctor_extensions/revealjs_linear_navigation/extension.rb +31 -0
- data/lib/neo4j_asciidoctor_extensions/revealjs_speaker_notes_aggregator.rb +8 -0
- data/lib/neo4j_asciidoctor_extensions/revealjs_speaker_notes_aggregator/extension.rb +50 -0
- data/neo4j-asciidoctor-extensions.gemspec +29 -0
- data/spec/.rubocop.yml +5 -0
- data/spec/cypher_syntax_role_spec.rb +29 -0
- data/spec/document_metadata_generator_spec.rb +78 -0
- data/spec/inline_highlighter_rouge_spec.rb +30 -0
- data/spec/revealjs_linear_navigation_spec.rb +52 -0
- data/spec/revealjs_speaker_notes_aggregator_spec.rb +33 -0
- data/tasks/bundler.rake +6 -0
- data/tasks/lint.rake +5 -0
- data/tasks/rspec.rake +8 -0
- metadata +182 -0
data/Rakefile
ADDED
@@ -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? '*<>'
|
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(/\*<>$/, '')
|
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('→', '->') if raw_text.include? '&'
|
38
|
+
raw_text = raw_text.gsub('←', '<-') 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
|