neo4j-asciidoctor-extensions 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|