hs-cli 0.1.2

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.hs.yml +2 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +8 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +5 -0
  8. data/Gemfile +8 -0
  9. data/Gemfile.lock +86 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +54 -0
  12. data/Rakefile +12 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/hs.gemspec +41 -0
  16. data/lib/hs/chapter.rb +31 -0
  17. data/lib/hs/config.rb +40 -0
  18. data/lib/hs/course.rb +35 -0
  19. data/lib/hs/elements/br.rb +9 -0
  20. data/lib/hs/elements/code.rb +22 -0
  21. data/lib/hs/elements/dd.rb +9 -0
  22. data/lib/hs/elements/dl.rb +9 -0
  23. data/lib/hs/elements/dt.rb +9 -0
  24. data/lib/hs/elements/element.rb +46 -0
  25. data/lib/hs/elements/emphasize.rb +9 -0
  26. data/lib/hs/elements/header.rb +16 -0
  27. data/lib/hs/elements/image.rb +22 -0
  28. data/lib/hs/elements/intro.rb +9 -0
  29. data/lib/hs/elements/li.rb +9 -0
  30. data/lib/hs/elements/link.rb +21 -0
  31. data/lib/hs/elements/note.rb +9 -0
  32. data/lib/hs/elements/ol.rb +9 -0
  33. data/lib/hs/elements/original.rb +9 -0
  34. data/lib/hs/elements/paragraph.rb +9 -0
  35. data/lib/hs/elements/strong.rb +9 -0
  36. data/lib/hs/elements/table.rb +39 -0
  37. data/lib/hs/elements/text.rb +21 -0
  38. data/lib/hs/elements/ul.rb +9 -0
  39. data/lib/hs/loaders/chapter_loader.rb +38 -0
  40. data/lib/hs/loaders/course_loader.rb +36 -0
  41. data/lib/hs/loaders/element_loader.rb +127 -0
  42. data/lib/hs/loaders/xml_parser.rb +62 -0
  43. data/lib/hs/preview/chapter_preview.rb +49 -0
  44. data/lib/hs/preview/course_preview.rb +43 -0
  45. data/lib/hs/preview/element_preview.rb +45 -0
  46. data/lib/hs/preview/html_preview.rb +27 -0
  47. data/lib/hs/version.rb +5 -0
  48. data/lib/hs.rb +28 -0
  49. metadata +231 -0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HS
4
+ class Table < HS::Element
5
+ def tag_name
6
+ 'table'
7
+ end
8
+ end
9
+
10
+ class Thead < HS::Element
11
+ def tag_name
12
+ 'thead'
13
+ end
14
+ end
15
+
16
+ class Tbody < HS::Element
17
+ def tag_name
18
+ 'tbody'
19
+ end
20
+ end
21
+
22
+ class Tr < HS::Element
23
+ def tag_name
24
+ 'tr'
25
+ end
26
+ end
27
+
28
+ class Th < HS::Element
29
+ def tag_name
30
+ 'th'
31
+ end
32
+ end
33
+
34
+ class Td < HS::Element
35
+ def tag_name
36
+ 'td'
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hs/preview/element_preview'
4
+
5
+ module HS
6
+ class Text
7
+ include ElementPreview
8
+
9
+ attr_accessor :text
10
+
11
+ def initialize(args)
12
+ args.each do |key, val|
13
+ instance_variable_set("@#{key}", val)
14
+ end
15
+ end
16
+
17
+ def preview
18
+ text_preview(text)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HS
4
+ class Ul < HS::Element
5
+ def tag_name
6
+ 'ul'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hs/loaders/xml_parser'
4
+ require 'hs/loaders/element_loader'
5
+
6
+ module HS
7
+ module ChapterLoader
8
+ include HS::XMLParser
9
+ include HS::ElementLoader
10
+
11
+ def load_chapter(course, xml)
12
+ create_chapter(course, parse_xml(xml))
13
+ end
14
+
15
+ private
16
+
17
+ def create_chapter(course, data) # rubocop:disable Metrics/MethodLength
18
+ chapter = HS::Chapter.new(course, slug: data[:slug])
19
+
20
+ data[:_children].each do |child|
21
+ case child[:_name]
22
+ when :title
23
+ chapter.title = get_text(child)
24
+ when :intro
25
+ chapter.intro = load_element(child)
26
+ when :modules
27
+ # TODO: extract :modules
28
+ nil
29
+ else
30
+ element = load_element(child)
31
+ chapter.content << element if element
32
+ end
33
+ end
34
+
35
+ chapter
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hs/loaders/xml_parser'
4
+
5
+ module HS
6
+ module CourseLoader
7
+ include HS::XMLParser
8
+
9
+ def load_course(xml, &block)
10
+ create_course(parse_xml(xml), &block)
11
+ end
12
+
13
+ private
14
+
15
+ def create_course(data) # rubocop:disable Metrics/MethodLength
16
+ course = HS::Course.new(slug: data[:slug])
17
+
18
+ data[:_children].each do |child|
19
+ case child[:_name]
20
+ when :title
21
+ course.title = get_text(child)
22
+ when :chapters
23
+ if block_given?
24
+ files = child[:_children].map { |c| c[:file] }.compact
25
+
26
+ files.each do |file|
27
+ yield course, file
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ course
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HS
4
+ module ElementLoader
5
+ def load_element(data)
6
+ name = data[:_name]
7
+ element = initialize_element(name, data)
8
+ children = load_elements(data[:_children])
9
+ children.each { |child| element.children << child }
10
+
11
+ element
12
+ end
13
+
14
+ def load_elements(data)
15
+ return [] unless data
16
+ data.map { |child_data| load_element(child_data) }
17
+ end
18
+
19
+ private
20
+
21
+ def get_params(data, names = %i[class_name style])
22
+ names.map do |name|
23
+ data[name] ? [name, data[name]] : nil
24
+ end.compact.to_h
25
+ end
26
+
27
+ def initialize_header(data)
28
+ HS::Header.new(get_params(data, %i[level class_name style]))
29
+ end
30
+
31
+ def initialize_element(name, data)
32
+ send("initialize_#{name}", data)
33
+ end
34
+
35
+ def initialize_text(data)
36
+ HS::Text.new(text: data[:text])
37
+ end
38
+
39
+ def initialize_p(data)
40
+ HS::Paragraph.new(get_params(data))
41
+ end
42
+
43
+ def initialize_note(data)
44
+ HS::Note.new(get_params(data))
45
+ end
46
+
47
+ def initialize_intro(data)
48
+ HS::Intro.new(get_params(data))
49
+ end
50
+
51
+ def initialize_em(data)
52
+ HS::Emphasize.new(get_params(data))
53
+ end
54
+
55
+ def initialize_link(data)
56
+ HS::Link.new(get_params(data, %i[to class_name style]))
57
+ end
58
+
59
+ def initialize_original(data)
60
+ HS::Original.new(get_params(data))
61
+ end
62
+
63
+ def initialize_strong(data)
64
+ HS::Strong.new(get_params(data))
65
+ end
66
+
67
+ def initialize_ul(data)
68
+ HS::Ul.new(get_params(data))
69
+ end
70
+
71
+ def initialize_ol(data)
72
+ HS::Ol.new(get_params(data))
73
+ end
74
+
75
+ def initialize_li(data)
76
+ HS::Li.new(get_params(data))
77
+ end
78
+
79
+ def initialize_image(data)
80
+ HS::Image.new(get_params(data, %i[src class_name style]))
81
+ end
82
+
83
+ def initialize_code(data)
84
+ HS::Code.new(get_params(data, %i[lang class_name style]))
85
+ end
86
+
87
+ def initialize_dd(data)
88
+ HS::Dd.new(get_params(data))
89
+ end
90
+
91
+ def initialize_dl(data)
92
+ HS::Dl.new(get_params(data))
93
+ end
94
+
95
+ def initialize_dt(data)
96
+ HS::Dt.new(get_params(data))
97
+ end
98
+
99
+ def initialize_table(data)
100
+ HS::Table.new(get_params(data))
101
+ end
102
+
103
+ def initialize_thead(data)
104
+ HS::Thead.new(get_params(data))
105
+ end
106
+
107
+ def initialize_tbody(data)
108
+ HS::Tbody.new(get_params(data))
109
+ end
110
+
111
+ def initialize_tr(data)
112
+ HS::Tr.new(get_params(data))
113
+ end
114
+
115
+ def initialize_th(data)
116
+ HS::Th.new(get_params(data))
117
+ end
118
+
119
+ def initialize_td(data)
120
+ HS::Td.new(get_params(data))
121
+ end
122
+
123
+ def initialize_br(_data)
124
+ HS::Br.new # no params!
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/inflector'
4
+ require 'nokogiri'
5
+
6
+ module HS
7
+ module XMLParser
8
+ def parse_xml(xml)
9
+ doc = Nokogiri::XML(xml) do |config|
10
+ config.options = Nokogiri::XML::ParseOptions::STRICT
11
+ end
12
+
13
+ to_object_notation(doc.root)
14
+ end
15
+
16
+ # Text content of the node.
17
+ # Note, that we don't go deep into element tree.
18
+ def get_text(object)
19
+ object[:_children].map { |obj| obj[:text] }.compact.join(' ')
20
+ end
21
+
22
+ private
23
+
24
+ def attributes_hash(xml)
25
+ xml.attributes.map do |key, attr|
26
+ [key.underscore.to_sym, attr.value]
27
+ end.to_h
28
+ end
29
+
30
+ def to_object_notation(xml)
31
+ class_name = xml.class.name.split('::').last.downcase
32
+ send("#{class_name}_to_object_notation", xml)
33
+ end
34
+
35
+ def element_to_object_notation(xml)
36
+ data = attributes_hash(xml)
37
+ data[:_name] = xml.name.underscore.to_sym
38
+ data[:_children] = to_object_notation(xml.children)
39
+
40
+ data
41
+ end
42
+
43
+ def cdata_to_object_notation(xml)
44
+ text_to_object_notation(xml)
45
+ end
46
+
47
+ def text_to_object_notation(xml)
48
+ {
49
+ _name: :text,
50
+ text: xml.text
51
+ }
52
+ end
53
+
54
+ def nodeset_to_object_notation(xml)
55
+ xml.map { |node| to_object_notation(node) }.compact
56
+ end
57
+
58
+ def comment_to_object_notation(_xml)
59
+ nil
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hs/preview/html_preview'
4
+
5
+ module HS
6
+ module ChapterPreview
7
+ include HS::HTMLPreview
8
+
9
+ def preview
10
+ html_preview do
11
+ body_element
12
+ end
13
+ end
14
+
15
+ def toc_preview
16
+ "<li><a href=\"/#{slug}\">#{title.strip}</a></li>"
17
+ end
18
+
19
+ def save_preview
20
+ file = "#{HS::Config.preview_dir}/#{slug}.html"
21
+ File.open(file, 'w') { |f| f.write(preview) }
22
+ end
23
+
24
+ private
25
+
26
+ def body_element
27
+ [
28
+ '<body>',
29
+ title_preview,
30
+ intro_preview,
31
+ content_preview,
32
+ '</body>'
33
+ ]
34
+ end
35
+
36
+ def intro_preview
37
+ return unless intro
38
+ intro.preview
39
+ end
40
+
41
+ def title_preview
42
+ "<h1 class=\"title\">#{title.strip}</h1>"
43
+ end
44
+
45
+ def content_preview
46
+ content.map(&:preview).compact.join
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hs/preview/html_preview'
4
+
5
+ module HS
6
+ module CoursePreview
7
+ include HS::HTMLPreview
8
+
9
+ def preview
10
+ html_preview do
11
+ body_element
12
+ end
13
+ end
14
+
15
+ def save_preview
16
+ file = "#{HS::Config.preview_dir}/index.html"
17
+ File.open(file, 'w') { |f| f.write(preview) }
18
+ end
19
+
20
+ private
21
+
22
+ def body_element
23
+ [
24
+ '<body>',
25
+ title_preview,
26
+ toc_preview,
27
+ '</body>'
28
+ ].join("\n")
29
+ end
30
+
31
+ def title_preview
32
+ " <h1 class=\"title\">#{title.strip}</h1>"
33
+ end
34
+
35
+ def toc_preview
36
+ [
37
+ ' <ul>',
38
+ chapters.map(&:toc_preview).join("\n"),
39
+ ' </ul>'
40
+ ].join("\n")
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+
5
+ module HS
6
+ module ElementPreview
7
+ def text_preview(text)
8
+ return unless text
9
+ CGI.escapeHTML(text)
10
+ end
11
+
12
+ def element_preview(tag_name, params, children)
13
+ [
14
+ element_start(tag_name, params, children),
15
+ element_content(children),
16
+ element_end(tag_name, children)
17
+ ].join
18
+ end
19
+
20
+ private
21
+
22
+ def element_start(tag, params, children)
23
+ start = "<#{tag}"
24
+
25
+ unless params.empty?
26
+ params_str = params.map { |k, v| "#{k}=\"#{v}\"" }.join(' ')
27
+ start = "#{start} #{params_str}"
28
+ end
29
+
30
+ return "#{start} />" if children.empty?
31
+ "#{start}>"
32
+ end
33
+
34
+ def element_content(children)
35
+ return if children.empty?
36
+ children.map(&:preview).compact.join
37
+ end
38
+
39
+ def element_end(tag, children)
40
+ return if children.empty?
41
+
42
+ "</#{tag}>"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HS
4
+ module HTMLPreview
5
+ def html_preview
6
+ body = yield
7
+ [
8
+ '<html>',
9
+ head_element,
10
+ body,
11
+ '</html>'
12
+ ].compact.join("\n")
13
+ end
14
+
15
+ private
16
+
17
+ def head_element
18
+ [
19
+ '<head>',
20
+ '<meta charset="utf-8"/> ',
21
+ '<meta name="viewport" content="width=device-width, initial-scale=1"/>',
22
+ '<link rel="stylesheet" href="/default.css"/>',
23
+ '</head>'
24
+ ].join("\n")
25
+ end
26
+ end
27
+ end
data/lib/hs/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HS
4
+ VERSION = '0.1.2'
5
+ end
data/lib/hs.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hs/version'
4
+ require 'hs/config'
5
+
6
+ require 'hs/course'
7
+ require 'hs/chapter'
8
+
9
+ require 'hs/elements/element'
10
+ require 'hs/elements/br'
11
+ require 'hs/elements/code'
12
+ require 'hs/elements/dd'
13
+ require 'hs/elements/dl'
14
+ require 'hs/elements/dt'
15
+ require 'hs/elements/emphasize'
16
+ require 'hs/elements/header'
17
+ require 'hs/elements/image'
18
+ require 'hs/elements/intro'
19
+ require 'hs/elements/li'
20
+ require 'hs/elements/link'
21
+ require 'hs/elements/note'
22
+ require 'hs/elements/ol'
23
+ require 'hs/elements/original'
24
+ require 'hs/elements/paragraph'
25
+ require 'hs/elements/strong'
26
+ require 'hs/elements/table'
27
+ require 'hs/elements/text'
28
+ require 'hs/elements/ul'