slacken 0.1.0 → 0.1.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.
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