slacken 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/README.md +5 -3
  4. data/{sample → examples}/out.txt +0 -0
  5. data/{sample → examples}/source.html +0 -0
  6. data/lib/slacken/document_component.rb +23 -36
  7. data/lib/slacken/dom_container.rb +1 -1
  8. data/lib/slacken/filter.rb +27 -0
  9. data/lib/slacken/filters.rb +19 -0
  10. data/lib/slacken/filters/elim_blanks.rb +41 -0
  11. data/lib/slacken/filters/elim_invalid_links.rb +33 -0
  12. data/lib/slacken/filters/elim_line_breaks.rb +36 -0
  13. data/lib/slacken/filters/extract_img_alt.rb +18 -0
  14. data/lib/slacken/filters/group_indent.rb +28 -0
  15. data/lib/slacken/filters/group_inlines.rb +31 -0
  16. data/lib/slacken/filters/sanitize_headline.rb +46 -0
  17. data/lib/slacken/filters/sanitize_link.rb +48 -0
  18. data/lib/slacken/filters/sanitize_list.rb +57 -0
  19. data/lib/slacken/filters/sanitize_table.rb +44 -0
  20. data/lib/slacken/filters/stringfy_checkbox.rb +26 -0
  21. data/lib/slacken/filters/stringfy_emoji.rb +26 -0
  22. data/lib/slacken/node_type.rb +2 -1
  23. data/lib/slacken/render_element.rb +2 -0
  24. data/lib/slacken/rendering.rb +0 -1
  25. data/lib/slacken/version.rb +1 -1
  26. data/scripts/update_markup_fixture.rb +3 -3
  27. data/spec/slacken/document_component_spec.rb +6 -8
  28. data/spec/slacken/filters/elim_blanks_spec.rb +32 -0
  29. data/spec/slacken/filters/elim_invalid_links_spec.rb +47 -0
  30. data/spec/slacken/filters/elim_line_breaks_spec.rb +39 -0
  31. data/spec/slacken/filters/group_indent_spec.rb +35 -0
  32. data/spec/slacken/filters/group_inlines_spec.rb +31 -0
  33. data/spec/slacken/filters/sanitize_headline_spec.rb +29 -0
  34. data/spec/slacken/filters/sanitize_link_spec.rb +23 -0
  35. data/spec/slacken/filters/sanitize_list_spec.rb +36 -0
  36. data/spec/slacken/filters/sanitize_table_spec.rb +39 -0
  37. data/spec/slacken_spec.rb +3 -0
  38. data/spec/spec_helper.rb +1 -2
  39. metadata +37 -26
  40. data/lib/slacken/document_component/elim_blanks.rb +0 -36
  41. data/lib/slacken/document_component/elim_invalid_links.rb +0 -26
  42. data/lib/slacken/document_component/elim_line_breaks.rb +0 -27
  43. data/lib/slacken/document_component/extract_img_alt.rb +0 -12
  44. data/lib/slacken/document_component/group_indent.rb +0 -27
  45. data/lib/slacken/document_component/group_inlines.rb +0 -26
  46. data/lib/slacken/document_component/sanitize_link_containers.rb +0 -40
  47. data/lib/slacken/document_component/sanitize_special_tag_containers.rb +0 -102
  48. data/lib/slacken/document_component/stringfy_checkbox.rb +0 -20
  49. data/lib/slacken/document_component/stringfy_emoji.rb +0 -20
  50. data/spec/slacken/document_component/elim_blanks_spec.rb +0 -34
  51. data/spec/slacken/document_component/elim_invalid_links_spec.rb +0 -49
  52. data/spec/slacken/document_component/elim_line_breaks_spec.rb +0 -41
  53. data/spec/slacken/document_component/group_indent_spec.rb +0 -37
  54. data/spec/slacken/document_component/group_inlines_spec.rb +0 -33
  55. data/spec/slacken/document_component/sanitize_special_tag_containers_spec.rb +0 -64
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ffc05a0da1b3b3fc3534666b1de2a36697448557
4
- data.tar.gz: 7d0fda248b1f4a64500e5e6e92b3941adc8320ab
3
+ metadata.gz: 5fc0f32867586c7b5d1bcc0f8d6f50a7397c8fc7
4
+ data.tar.gz: 437c297ac0aec496d3002bc88770fce4919a5ec7
5
5
  SHA512:
6
- metadata.gz: 8ee43ac2dbef90cf74aced8f4e940dde59d80bbbd693fdcc7d822624ea21f51b8a7a13e19345dc9d06cf3363c6c8e1c168e9811f2808484e7f7cc7dfcf0c9067
7
- data.tar.gz: 19f4898e6ee95503572ea4bb70005f54d84779f647c93c1c982250685bed83b7f95acf7a4506347f8bc535776e4fd18864edb871bff1f71b071673babde0105d
6
+ metadata.gz: 4e4283f2ff319d82403f1465b2e8fe74da8a7f2e6d61e3489bfd44224038e7e719e8cba5165a88b8fd3902ed4113810b0baf51e392450fec8d7fca1554251a47
7
+ data.tar.gz: 6ee2318990b5e0a67579a8114f5e9f1a5b8215b21b5f6110c717c3b154db4b5ae08a962628b057a97124eaa043ce9d5f0ce6f2cda80fd715b8c7b9c2311fecc0
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm: 2.2
3
+ script: bundle exec rake spec
data/README.md CHANGED
@@ -1,15 +1,17 @@
1
- # slacken
1
+ # slacken [![Build Status](https://travis-ci.org/increments/slacken.svg)](https://travis-ci.org/increments/slacken)
2
2
 
3
3
  ## Description
4
4
  This gem translates a html source into a markup text for Slack.
5
5
 
6
+ Official description of Slack message formatting is [here](https://api.slack.com/docs/formatting).
7
+
6
8
  ## Examples
7
9
 
8
- Sample input source and output texts are in `sample/`.
10
+ Sample input source and output texts are in `examples/`.
9
11
 
10
12
  ```
11
13
  > require 'slacken'
12
- > puts Slacken.translate(File.read('sample/source.html'))
14
+ > puts Slacken.translate(File.read('examples/source.html'))
13
15
  # *Slacken*
14
16
  #
15
17
  # This gem translates a html source into *a markup text for Slack*.
File without changes
File without changes
@@ -1,41 +1,36 @@
1
1
  require 'forwardable'
2
+ require 'slacken/filters'
2
3
 
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
4
  module Slacken
5
+ # Public: An intermediate object that is used when a HTML source is translated into a MarkupElement
6
+ # representing structure of a markup text.
7
+ # A DocumentComponent has tree structure and has child nodes as `children`.
18
8
  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
9
+ NormalizeFilters = [
10
+ Filters::StringfyEmoji,
11
+ Filters::StringfyCheckbox,
12
+ Filters::ExtractImgAlt,
13
+ Filters::ElimInvalidLinks,
14
+ Filters::SanitizeHeadline,
15
+ Filters::SanitizeLink,
16
+ Filters::SanitizeList,
17
+ Filters::SanitizeTable,
18
+ Filters::GroupInlines,
19
+ Filters::GroupIndent,
20
+ Filters::ElimBlanks,
21
+ Filters::ElimLineBreaks,
22
+ ]
29
23
 
30
24
  extend Forwardable
31
25
  def_delegators :@type, :block?, :inline?
32
26
 
33
- attr_reader :type, :attrs, :children
27
+ attr_reader :type, :attrs, :children, :marks
34
28
 
35
29
  def initialize(type, children = [], attrs = {})
36
30
  @type = NodeType.create(type)
37
31
  @attrs = attrs
38
32
  @children = children
33
+ @marks = {}
39
34
  end
40
35
 
41
36
  def derive(new_children, updates = {})
@@ -49,19 +44,11 @@ module Slacken
49
44
  end
50
45
 
51
46
  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
47
+ NormalizeFilters.reduce(self) do |component, filter_klass|
48
+ filter_klass.new.call(component)
49
+ end
62
50
  end
63
51
 
64
- # Private: Convert this element to a MarkupElement.
65
52
  def produce_element
66
53
  if type.member_of?(:table)
67
54
  TableElement.new(children.map(&:produce_element))
@@ -1,7 +1,7 @@
1
1
  require 'nokogiri'
2
2
 
3
- # Public: a DOM tree container parsed by Nokogiri.
4
3
  module Slacken
4
+ # Public: a DOM tree container parsed by Nokogiri.
5
5
  class DomContainer
6
6
  attr_reader :root
7
7
 
@@ -0,0 +1,27 @@
1
+ module Slacken
2
+ # Public: Base class of filters for DocumentComponent.
3
+ class Filter
4
+ attr_reader :options
5
+ def initialize(options = {})
6
+ @options = options
7
+ end
8
+
9
+ # Public: Create a new refined DocumentComponent from the given component.
10
+ #
11
+ # component - A DocumentComponent object.
12
+ #
13
+ # Returns a new refined DocumentComponent object.
14
+ def call(component)
15
+ fail NotImplementedError
16
+ end
17
+
18
+ # Public: Check if the given component passes postcondition of the filter.
19
+ #
20
+ # component - A DocumentComponent object to check.
21
+ #
22
+ # Returns true if the component passes the postcondition.
23
+ def valid?(component)
24
+ true
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ module Slacken
2
+ module Filters
3
+ end
4
+ end
5
+
6
+ require 'slacken/filter'
7
+
8
+ require 'slacken/filters/elim_blanks'
9
+ require 'slacken/filters/elim_invalid_links'
10
+ require 'slacken/filters/elim_line_breaks'
11
+ require 'slacken/filters/extract_img_alt'
12
+ require 'slacken/filters/group_inlines'
13
+ require 'slacken/filters/group_indent'
14
+ require 'slacken/filters/sanitize_headline'
15
+ require 'slacken/filters/sanitize_link'
16
+ require 'slacken/filters/sanitize_list'
17
+ require 'slacken/filters/sanitize_table'
18
+ require 'slacken/filters/stringfy_checkbox'
19
+ require 'slacken/filters/stringfy_emoji'
@@ -0,0 +1,41 @@
1
+ module Slacken::Filters
2
+ # Public: Reject blank elements.
3
+ class ElimBlanks < Slacken::Filter
4
+ def call(component)
5
+ if component.type.member_of?(:pre)
6
+ component
7
+ else
8
+ component.derive(
9
+ component.children.reject(&method(:blank?)).map(&method(:call))
10
+ )
11
+ end
12
+ end
13
+
14
+ def valid?(component)
15
+ if component.type.member_of?(:pre)
16
+ true
17
+ else
18
+ !blank?(component) && component.children.all?(&method(:valid?))
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def blank?(component)
25
+ # Reduce complexity of calculation by marking.
26
+ # (`blank?` traces the given tree to its leaf nodes.)
27
+ return component.marks[:blank] if component.marks.has_key?(:blank)
28
+
29
+ component.marks[:blank] =
30
+ case component.type.name
31
+ when :pre, :ul, :li, :br, :hr, :img, :checkbox
32
+ false
33
+ when :text, :emoji
34
+ content = component.attrs[:content]
35
+ content.nil? || !content.match(/\A\s*\Z/).nil?
36
+ else
37
+ component.children.empty? || component.children.all?(&method(:blank?))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ module Slacken::Filters
2
+ # Public: Eliminate internal links and blank links.
3
+ class ElimInvalidLinks < Slacken::Filter
4
+ def call(component)
5
+ if invalid_link?(component)
6
+ component.derive(
7
+ component.children.map(&method(:call)),
8
+ type: :span
9
+ )
10
+ else
11
+ component.derive(
12
+ component.children.map(&method(:call)),
13
+ )
14
+ end
15
+ end
16
+
17
+ def valid?(component)
18
+ return false if invalid_link?(component)
19
+ component.children.all?(&method(:valid?))
20
+ end
21
+
22
+ private
23
+
24
+ def invalid_link?(component)
25
+ if component.type.member_of?(:a)
26
+ link = component.attrs[:href]
27
+ link.nil? || !link.match(%r{\Ahttps?://})
28
+ else
29
+ false
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ module Slacken::Filters
2
+ # Public: Remove line breaks from texts.
3
+ class ElimLineBreaks < Slacken::Filter
4
+ def call(component)
5
+ case component.type.name
6
+ when :pre
7
+ component
8
+ when :text
9
+ new_content = component.attrs[:content].gsub(/[\r\n]/, '')
10
+ component.derive(
11
+ component.children,
12
+ attrs: component.attrs.merge(content: new_content)
13
+ )
14
+ else
15
+ component.derive(
16
+ component.children.map(&method(:call))
17
+ )
18
+ end
19
+ end
20
+
21
+ def valid?(component)
22
+ has_no_line_breaks?(component)
23
+ end
24
+
25
+ def has_no_line_breaks?(component)
26
+ case component.type.name
27
+ when :pre
28
+ true
29
+ when :text
30
+ !component.attrs[:content].match(/[\r\n]/)
31
+ else
32
+ component.children.all?(&method(:has_no_line_breaks?))
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ module Slacken::Filters
2
+ # Public: Convert alt attribute of img node to child text node.
3
+ class ExtractImgAlt < Slacken::Filter
4
+ def call(component)
5
+ if component.type.member_of?(:img)
6
+ component.derive([
7
+ component.class.new(
8
+ :text, [], content: component.attrs[:alt] || component.attrs[:src]
9
+ )
10
+ ])
11
+ else
12
+ component.derive(
13
+ component.children.map(&method(:call))
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module Slacken::Filters
2
+ # Public: Wrap child content nodes of each li or dd node with an indent node.
3
+ class GroupIndent < Slacken::Filter
4
+ def call(component)
5
+ if component.type.member_of?(%i(li dd))
6
+ head, *tails = component.children.map(&method(:call))
7
+ component.derive([head, component.class.new(:indent, tails)])
8
+ else
9
+ component.derive(
10
+ component.children.map(&method(:call))
11
+ )
12
+ end
13
+ end
14
+
15
+ def valid?(component)
16
+ if component.type.member_of?(%i(li dd))
17
+ head, tail = component.children
18
+ if tail
19
+ tail.type.member_of?(:indent) && valid?(tail)
20
+ else
21
+ true
22
+ end
23
+ else
24
+ component.children.all?(&method(:valid?))
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ module Slacken::Filters
2
+ # Public: Group inline elements and wrap them in a wrapper node.
3
+ # Wrapper nodes are introduced to group inline nodes in a paragraph.
4
+ class GroupInlines < Slacken::Filter
5
+ def call(component)
6
+ if component.block?
7
+ component.derive(group_component(component))
8
+ else
9
+ component.derive(component.children.map(&method(:call)))
10
+ end
11
+ end
12
+
13
+ def valid?(component)
14
+ if component.type.member_of?(:wrapper)
15
+ true
16
+ elsif component.inline?
17
+ false
18
+ else
19
+ component.children.all?(&method(:valid?))
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def group_component(component)
26
+ component.children.map(&method(:call)).chunk(&:inline?).map do |is_inline, group|
27
+ is_inline ? component.class.new(:wrapper, group) : group
28
+ end.flatten
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ module Slacken::Filters
2
+ # Public: Sanitize not allowed tags in headline.
3
+ class SanitizeHeadline < Slacken::Filter
4
+ def call(component)
5
+ # NOTE: each special tag (list, headline, and table) is not allowed to occur
6
+ # in another type special tags.
7
+ if component.type.name =~ /h\d/
8
+ component.derive(component.children.map(&method(:sanitize_headline)))
9
+ else
10
+ component.derive(component.children.map(&method(:call)))
11
+ end
12
+ end
13
+
14
+ def valid?(component)
15
+ if component.type.name =~ /h\d/
16
+ component.children.all?(&method(:headline_sanitized?))
17
+ else
18
+ component.children.all?(&method(:valid?))
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def sanitize_headline(component)
25
+ if component.type.allowed_in_headline?
26
+ component.derive(
27
+ component.children.map(&method(:sanitize_headline))
28
+ )
29
+ else
30
+ # No block tags are allowed.
31
+ component.derive(
32
+ component.children.map(&method(:sanitize_headline)),
33
+ type: :span
34
+ )
35
+ end
36
+ end
37
+
38
+ def headline_sanitized?(component)
39
+ if component.type.allowed_in_headline?
40
+ component.children.all?(&method(:headline_sanitized?))
41
+ else
42
+ false
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ module Slacken::Filters
2
+ # Public: Sanitize not allowed tags in links.
3
+ class SanitizeLink < Slacken::Filter
4
+ def call(component)
5
+ if component.type.member_of?(%i(img a iframe))
6
+ component.derive(
7
+ component.children.map(&method(:sanitize))
8
+ )
9
+ else
10
+ component.derive(
11
+ component.children.map(&method(:call))
12
+ )
13
+ end
14
+ end
15
+
16
+ def valid?(component)
17
+ if component.type.member_of?(%i(img a iframe))
18
+ component.children.all?(&method(:link_containers_sanitized?))
19
+ else
20
+ component.children.all?(&method(:valid?))
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def sanitize(component)
27
+ if component.type.allowed_in_link?
28
+ component.derive(
29
+ component.children.map(&method(:sanitize))
30
+ )
31
+ else
32
+ # No block tags are allowed.
33
+ component.derive(
34
+ component.children.map(&method(:sanitize)),
35
+ type: :span
36
+ )
37
+ end
38
+ end
39
+
40
+ def link_containers_sanitized?(component)
41
+ if component.type.allowed_in_link?
42
+ component.children.all?(&method(:link_containers_sanitized?))
43
+ else
44
+ false
45
+ end
46
+ end
47
+ end
48
+ end