slacken 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +60 -0
  7. data/Rakefile +36 -0
  8. data/lib/slacken.rb +21 -0
  9. data/lib/slacken/document_component.rb +73 -0
  10. data/lib/slacken/document_component/elim_blanks.rb +36 -0
  11. data/lib/slacken/document_component/elim_invalid_links.rb +26 -0
  12. data/lib/slacken/document_component/elim_line_breaks.rb +27 -0
  13. data/lib/slacken/document_component/extract_img_alt.rb +12 -0
  14. data/lib/slacken/document_component/group_indent.rb +27 -0
  15. data/lib/slacken/document_component/group_inlines.rb +26 -0
  16. data/lib/slacken/document_component/sanitize_link_containers.rb +40 -0
  17. data/lib/slacken/document_component/sanitize_special_tag_containers.rb +102 -0
  18. data/lib/slacken/document_component/stringfy_checkbox.rb +20 -0
  19. data/lib/slacken/document_component/stringfy_emoji.rb +20 -0
  20. data/lib/slacken/dom_container.rb +45 -0
  21. data/lib/slacken/node_type.rb +53 -0
  22. data/lib/slacken/render_element.rb +91 -0
  23. data/lib/slacken/rendering.rb +102 -0
  24. data/lib/slacken/slack_url.rb +7 -0
  25. data/lib/slacken/table_element.rb +23 -0
  26. data/lib/slacken/version.rb +3 -0
  27. data/sample/out.txt +38 -0
  28. data/sample/source.html +55 -0
  29. data/scripts/update_markup_fixture.rb +8 -0
  30. data/slacken.gemspec +28 -0
  31. data/spec/fixtures/example.html +114 -0
  32. data/spec/fixtures/markup_text.txt +73 -0
  33. data/spec/helpers/document_component_dsl.rb +11 -0
  34. data/spec/slacken/document_component/elim_blanks_spec.rb +34 -0
  35. data/spec/slacken/document_component/elim_invalid_links_spec.rb +49 -0
  36. data/spec/slacken/document_component/elim_line_breaks_spec.rb +41 -0
  37. data/spec/slacken/document_component/group_indent_spec.rb +37 -0
  38. data/spec/slacken/document_component/group_inlines_spec.rb +33 -0
  39. data/spec/slacken/document_component/sanitize_special_tag_containers_spec.rb +64 -0
  40. data/spec/slacken/document_component_spec.rb +19 -0
  41. data/spec/slacken_spec.rb +12 -0
  42. data/spec/spec_helper.rb +13 -0
  43. metadata +194 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ffc05a0da1b3b3fc3534666b1de2a36697448557
4
+ data.tar.gz: 7d0fda248b1f4a64500e5e6e92b3941adc8320ab
5
+ SHA512:
6
+ metadata.gz: 8ee43ac2dbef90cf74aced8f4e940dde59d80bbbd693fdcc7d822624ea21f51b8a7a13e19345dc9d06cf3363c6c8e1c168e9811f2808484e7f7cc7dfcf0c9067
7
+ data.tar.gz: 19f4898e6ee95503572ea4bb70005f54d84779f647c93c1c982250685bed83b7f95acf7a4506347f8bc535776e4fd18864edb871bff1f71b071673babde0105d
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ html/
3
+ pkg/
4
+ vendor/cache/*.gem
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Tomoya Chiba
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,60 @@
1
+ # slacken
2
+
3
+ ## Description
4
+ This gem translates a html source into a markup text for Slack.
5
+
6
+ ## Examples
7
+
8
+ Sample input source and output texts are in `sample/`.
9
+
10
+ ```
11
+ > require 'slacken'
12
+ > puts Slacken.translate(File.read('sample/source.html'))
13
+ # *Slacken*
14
+ #
15
+ # This gem translates a html source into *a markup text for Slack*.
16
+ # <http://qiita.com|Qiita> uses this gem to decorate notification messages to Slack :trollface:.
17
+ #
18
+ # *Examples*
19
+ #
20
+ # *List*
21
+ #
22
+ # 1. Item 1
23
+ # 2. _Item 2 (italic)_
24
+ # • [x] Checked
25
+ # • [ ] Unchecked
26
+ # 3. *Item 3 (bold)*
27
+ # • Nested Item 1
28
+ # • Nested Item 2
29
+ #
30
+ # *Citation*
31
+ #
32
+ # > Qiita is a technical information sharing site for programmers.
33
+ # > Kobito is an application for technical information recording.
34
+ #
35
+ # *Source Code*
36
+ #
37
+ # ```class World
38
+ # def hello
39
+ # puts 'Hello, world!'
40
+ # end
41
+ # end
42
+ # ```
43
+ #
44
+ # *Image*
45
+ #
46
+ # This is a Qiita logo.
47
+ #
48
+ # -----------
49
+ #
50
+ # <http://cdn.qiita.com/assets/siteid-reverse-1949e989f9d8b2f6fad65a57292b2b01.png|Qiita logo>
51
+ ```
52
+
53
+ ## Install
54
+
55
+ ```
56
+ $ gem install slacken
57
+ ```
58
+
59
+ ## License
60
+ MIT
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler'
7
+ rescue LoadError => e
8
+ warn e.message
9
+ warn "Run `gem install bundler` to install Bundler."
10
+ exit -1
11
+ end
12
+
13
+ begin
14
+ Bundler.setup(:development)
15
+ rescue Bundler::BundlerError => e
16
+ warn e.message
17
+ warn "Run `bundle install` to install missing gems."
18
+ exit e.status_code
19
+ end
20
+
21
+ require 'rake'
22
+
23
+ require 'rubygems/tasks'
24
+ Gem::Tasks.new
25
+
26
+ require 'rdoc/task'
27
+ RDoc::Task.new do |rdoc|
28
+ rdoc.title = "slacken"
29
+ end
30
+ task :doc => :rdoc
31
+
32
+ require 'rspec/core/rake_task'
33
+ RSpec::Core::RakeTask.new
34
+
35
+ task :test => :spec
36
+ task :default => :spec
@@ -0,0 +1,21 @@
1
+ module Slacken
2
+ require 'slacken/document_component'
3
+ require 'slacken/dom_container'
4
+ require 'slacken/render_element'
5
+ require 'slacken/rendering'
6
+ require 'slacken/node_type'
7
+ require 'slacken/slack_url'
8
+ require 'slacken/table_element'
9
+ require 'slacken/version'
10
+
11
+ class << self
12
+ def translate(html_source)
13
+ convert(html_source).to_s
14
+ end
15
+
16
+ def convert(html_source)
17
+ Slacken::DomContainer.parse_html(html_source)
18
+ .to_component.to_element
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ require 'forwardable'
2
+
3
+ require 'slacken/document_component/elim_blanks'
4
+ require 'slacken/document_component/elim_invalid_links'
5
+ require 'slacken/document_component/elim_line_breaks'
6
+ require 'slacken/document_component/extract_img_alt'
7
+ require 'slacken/document_component/group_inlines'
8
+ require 'slacken/document_component/group_indent'
9
+ require 'slacken/document_component/sanitize_link_containers'
10
+ require 'slacken/document_component/sanitize_special_tag_containers'
11
+ require 'slacken/document_component/stringfy_checkbox'
12
+ require 'slacken/document_component/stringfy_emoji'
13
+
14
+ # Public: An intermediate object that is used when a HTML source is translated into a MarkupElement
15
+ # representing structure of a markup text.
16
+ # A DocumentComponent has tree structure and has child nodes as `children`.
17
+ module Slacken
18
+ class DocumentComponent
19
+ include ElimBlanks
20
+ include ElimInvalidLinks
21
+ include ElimLineBreaks
22
+ include ExtractImgAlt
23
+ include GroupInlines
24
+ include GroupIndent
25
+ include SanitizeLinkContainers
26
+ include SanitizeSpecialTagContainers
27
+ include StringfyCheckbox
28
+ include StringfyEmoji
29
+
30
+ extend Forwardable
31
+ def_delegators :@type, :block?, :inline?
32
+
33
+ attr_reader :type, :attrs, :children
34
+
35
+ def initialize(type, children = [], attrs = {})
36
+ @type = NodeType.create(type)
37
+ @attrs = attrs
38
+ @children = children
39
+ end
40
+
41
+ def derive(new_children, updates = {})
42
+ self.class.new(
43
+ updates[:type] || type, new_children, updates[:attrs] || attrs)
44
+ end
45
+
46
+ # Public: Normalize this object's structure and convert it to MarkupElement.
47
+ def to_element
48
+ normalize.produce_element
49
+ end
50
+
51
+ def normalize
52
+ stringfy_emoji
53
+ .stringfy_checkbox
54
+ .extract_img_alt
55
+ .elim_invalid_links
56
+ .sanitize_link_containers
57
+ .sanitize_special_tag_containers
58
+ .group_inlines
59
+ .group_indent
60
+ .elim_blanks
61
+ .elim_line_breaks
62
+ end
63
+
64
+ # Private: Convert this element to a MarkupElement.
65
+ def produce_element
66
+ if type.member_of?(:table)
67
+ TableElement.new(children.map(&:produce_element))
68
+ else
69
+ RenderElement.new(type, children.map(&:produce_element), attrs)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,36 @@
1
+ class Slacken::DocumentComponent
2
+ module ElimBlanks
3
+ # Private: Reject blank elements
4
+ def elim_blanks
5
+ case type.name
6
+ when :pre
7
+ self
8
+ else
9
+ derive(children.reject(&:blank?).map(&:elim_blanks))
10
+ end
11
+ end
12
+
13
+ def blank?
14
+ return @is_blank if !@is_blank.nil?
15
+ @is_blank =
16
+ case type.name
17
+ when :pre, :ul, :li, :br, :hr, :img, :checkbox
18
+ false
19
+ when :text, :emoji
20
+ content = attrs[:content]
21
+ content.nil? || !content.match(/\A\s*\Z/).nil?
22
+ else
23
+ children.empty? || children.all?(&:blank?)
24
+ end
25
+ end
26
+
27
+ def has_no_blanks?
28
+ case type.name
29
+ when :pre
30
+ true
31
+ else
32
+ !blank? && children.all?(&:has_no_blanks?)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ class Slacken::DocumentComponent
2
+ module ElimInvalidLinks
3
+ # Private: Eliminate internal links and blank links
4
+ def elim_invalid_links
5
+ if invalid_link?
6
+ derive(children.map(&:elim_invalid_links), type: :span)
7
+ else
8
+ derive(children.map(&:elim_invalid_links))
9
+ end
10
+ end
11
+
12
+ def invalid_link?
13
+ if type.member_of?(:a)
14
+ link = attrs[:href]
15
+ link.nil? ||
16
+ !link.match(%r{\Ahttps?://})
17
+ else
18
+ false
19
+ end
20
+ end
21
+
22
+ def has_no_invalid_links?
23
+ !invalid_link? && children.all?(&:has_no_invalid_links?)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ class Slacken::DocumentComponent
2
+ module ElimLineBreaks
3
+ # Private: Reject blank elements
4
+ def elim_line_breaks
5
+ case type.name
6
+ when :text
7
+ new_content = attrs[:content].gsub(/[\r\n]/, '')
8
+ derive(children, attrs: attrs.merge(content: new_content))
9
+ when :pre
10
+ self
11
+ else
12
+ derive(children.map(&:elim_line_breaks))
13
+ end
14
+ end
15
+
16
+ def has_no_line_breaks?
17
+ case type.name
18
+ when :text
19
+ !attrs[:content].match(/[\r\n]/)
20
+ when :pre
21
+ true
22
+ else
23
+ children.all?(&:has_no_line_breaks?)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ class Slacken::DocumentComponent
2
+ module ExtractImgAlt
3
+ def extract_img_alt
4
+ if type.member_of?(:img)
5
+ child = self.class.new(:text, [], content: attrs[:alt] || attrs[:src])
6
+ derive([child])
7
+ else
8
+ derive(children.map(&:extract_img_alt))
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,27 @@
1
+ class Slacken::DocumentComponent
2
+ module GroupIndent
3
+ # Private: Wrap contents to indent.
4
+ # Childrens of li or dd tags are wrapped.
5
+ def group_indent
6
+ if type.member_of?(:li, :dd)
7
+ head, *tails = children.map(&:group_indent)
8
+ derive([head, self.class.new(:indent, tails)])
9
+ else
10
+ derive(children.map(&:group_indent))
11
+ end
12
+ end
13
+
14
+ def indent_grouped?
15
+ if type.member_of?(:li, :dd)
16
+ head, tail = children
17
+ if tail
18
+ tail.type.member_of?(:indent) && tail.indent_grouped?
19
+ else
20
+ true
21
+ end
22
+ else
23
+ children.all?(&:indent_grouped?)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ class Slacken::DocumentComponent
2
+ module GroupInlines
3
+ # Private: Group inline elements and wrap them in a wrapper node.
4
+ # Wrapper nodes are introduced to group inline nodes in a paragraph.
5
+ def group_inlines
6
+ if block?
7
+ new_children = children.map(&:group_inlines).chunk(&:inline?).map do |is_inline, group|
8
+ is_inline ? self.class.new(:wrapper, group) : group
9
+ end.flatten
10
+ derive(new_children)
11
+ else
12
+ derive(children.map(&:group_inlines))
13
+ end
14
+ end
15
+
16
+ def inlines_grouped?
17
+ if type.member_of?(:wrapper)
18
+ true
19
+ elsif inline?
20
+ false
21
+ else
22
+ children.all?(&:inlines_grouped?)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ class Slacken::DocumentComponent
2
+ module SanitizeLinkContainers
3
+ # Private: Sanitize not allowed tags in links.
4
+ def sanitize_link_containers
5
+ case type.name
6
+ when :img, :a, :iframe
7
+ derive(children.map(&:sanitize_in_link))
8
+ else
9
+ derive(children.map(&:sanitize_link_containers))
10
+ end
11
+ end
12
+
13
+ def sanitize_in_link
14
+ if type.allowed_in_link?
15
+ derive(children.map(&:sanitize_in_link))
16
+ else
17
+ # No block tags are allowed.
18
+ derive(children.map(&:sanitize_in_link), type: :span)
19
+ end
20
+ end
21
+
22
+ def link_sanitized?
23
+ case type.name
24
+ when :img, :a, :iframe
25
+ children.all?(&:list_containers_sanitized?)
26
+ else
27
+ children.all?(&:link_sanitized?)
28
+ end
29
+ end
30
+
31
+ def link_container_sanitized?
32
+ if type.allowed_in_link?
33
+ children.all?(&:link_containers_sanitized?)
34
+ else
35
+ false
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,102 @@
1
+ class Slacken::DocumentComponent
2
+ module SanitizeSpecialTagContainers
3
+ # Private: Sanitize not allowed tags in list, headline, and table.
4
+ def sanitize_special_tag_containers
5
+ # NOTE: each special tag (list, headline, and table) is not allowed to occur
6
+ # in another type special tags.
7
+ case type.name
8
+ when /h\d/
9
+ derive(children.map(&:sanitize_headline))
10
+ when :ul, :ol, :dl
11
+ derive(children.map(&:sanitize_list))
12
+ when :table
13
+ derive(children.map(&:sanitize_table))
14
+ else
15
+ derive(children.map(&:sanitize_special_tag_containers))
16
+ end
17
+ end
18
+
19
+ def sanitize_headline
20
+ if type.allowed_in_headline?
21
+ derive(children.map(&:sanitize_headline))
22
+ else
23
+ # No block tags are allowed.
24
+ derive(children.map(&:sanitize_headline), type: :span)
25
+ end
26
+ end
27
+
28
+ def sanitize_list
29
+ if type.member_of?(:li, :dd)
30
+ head, *tails = children
31
+ derive([head.sanitize_list_item, *tails.map(&:sanitize_list)])
32
+ elsif type.allowed_in_list?
33
+ derive(children.map(&:sanitize_list))
34
+ else
35
+ derive(children.map(&:sanitize_list), type: block? ? :div : :span)
36
+ end
37
+ end
38
+
39
+ def sanitize_list_item
40
+ if type.allowed_as_list_item?
41
+ derive(children.map(&:sanitize_list_item))
42
+ else
43
+ # No block tags are allowed.
44
+ derive(children.map(&:sanitize_list_item), type: :span)
45
+ end
46
+ end
47
+
48
+ def sanitize_table
49
+ if type.allowed_in_table?
50
+ derive(children.map(&:sanitize_table))
51
+ else
52
+ # No block tags are allowed.
53
+ derive(children.map(&:sanitize_table), type: :span)
54
+ end
55
+ end
56
+
57
+ def sanitized?
58
+ case type.name
59
+ when /h\d/
60
+ children.all?(&:headline_sanitized?)
61
+ when :ul, :ol, :dl
62
+ children.all?(&:list_sanitized?)
63
+ when :table
64
+ children.all?(&:table_sanitized?)
65
+ else
66
+ children.all?(&:sanitized?)
67
+ end
68
+ end
69
+
70
+ def headline_sanitized?
71
+ if type.allowed_in_headline?
72
+ children.all?(&:headline_sanitized?)
73
+ else
74
+ false
75
+ end
76
+ end
77
+
78
+ def list_sanitized?
79
+ if type.allowed_in_list?
80
+ children.all?(&:list_sanitized?)
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ def list_item_sanitized?
87
+ if type.allowed_as_list_item?
88
+ children.all?(&:list_item_sanitized?)
89
+ else
90
+ false
91
+ end
92
+ end
93
+
94
+ def table_sanitized?
95
+ if type.allowed_in_table?
96
+ children.all?(&:table_sanitized?)
97
+ else
98
+ false
99
+ end
100
+ end
101
+ end
102
+ end