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.
- checksums.yaml +4 -4
- data/.travis.yml +3 -0
- data/README.md +5 -3
- data/{sample → examples}/out.txt +0 -0
- data/{sample → examples}/source.html +0 -0
- data/lib/slacken/document_component.rb +23 -36
- data/lib/slacken/dom_container.rb +1 -1
- data/lib/slacken/filter.rb +27 -0
- data/lib/slacken/filters.rb +19 -0
- data/lib/slacken/filters/elim_blanks.rb +41 -0
- data/lib/slacken/filters/elim_invalid_links.rb +33 -0
- data/lib/slacken/filters/elim_line_breaks.rb +36 -0
- data/lib/slacken/filters/extract_img_alt.rb +18 -0
- data/lib/slacken/filters/group_indent.rb +28 -0
- data/lib/slacken/filters/group_inlines.rb +31 -0
- data/lib/slacken/filters/sanitize_headline.rb +46 -0
- data/lib/slacken/filters/sanitize_link.rb +48 -0
- data/lib/slacken/filters/sanitize_list.rb +57 -0
- data/lib/slacken/filters/sanitize_table.rb +44 -0
- data/lib/slacken/filters/stringfy_checkbox.rb +26 -0
- data/lib/slacken/filters/stringfy_emoji.rb +26 -0
- data/lib/slacken/node_type.rb +2 -1
- data/lib/slacken/render_element.rb +2 -0
- data/lib/slacken/rendering.rb +0 -1
- data/lib/slacken/version.rb +1 -1
- data/scripts/update_markup_fixture.rb +3 -3
- data/spec/slacken/document_component_spec.rb +6 -8
- data/spec/slacken/filters/elim_blanks_spec.rb +32 -0
- data/spec/slacken/filters/elim_invalid_links_spec.rb +47 -0
- data/spec/slacken/filters/elim_line_breaks_spec.rb +39 -0
- data/spec/slacken/filters/group_indent_spec.rb +35 -0
- data/spec/slacken/filters/group_inlines_spec.rb +31 -0
- data/spec/slacken/filters/sanitize_headline_spec.rb +29 -0
- data/spec/slacken/filters/sanitize_link_spec.rb +23 -0
- data/spec/slacken/filters/sanitize_list_spec.rb +36 -0
- data/spec/slacken/filters/sanitize_table_spec.rb +39 -0
- data/spec/slacken_spec.rb +3 -0
- data/spec/spec_helper.rb +1 -2
- metadata +37 -26
- data/lib/slacken/document_component/elim_blanks.rb +0 -36
- data/lib/slacken/document_component/elim_invalid_links.rb +0 -26
- data/lib/slacken/document_component/elim_line_breaks.rb +0 -27
- data/lib/slacken/document_component/extract_img_alt.rb +0 -12
- data/lib/slacken/document_component/group_indent.rb +0 -27
- data/lib/slacken/document_component/group_inlines.rb +0 -26
- data/lib/slacken/document_component/sanitize_link_containers.rb +0 -40
- data/lib/slacken/document_component/sanitize_special_tag_containers.rb +0 -102
- data/lib/slacken/document_component/stringfy_checkbox.rb +0 -20
- data/lib/slacken/document_component/stringfy_emoji.rb +0 -20
- data/spec/slacken/document_component/elim_blanks_spec.rb +0 -34
- data/spec/slacken/document_component/elim_invalid_links_spec.rb +0 -49
- data/spec/slacken/document_component/elim_line_breaks_spec.rb +0 -41
- data/spec/slacken/document_component/group_indent_spec.rb +0 -37
- data/spec/slacken/document_component/group_inlines_spec.rb +0 -33
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5fc0f32867586c7b5d1bcc0f8d6f50a7397c8fc7
|
4
|
+
data.tar.gz: 437c297ac0aec496d3002bc88770fce4919a5ec7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e4283f2ff319d82403f1465b2e8fe74da8a7f2e6d61e3489bfd44224038e7e719e8cba5165a88b8fd3902ed4113810b0baf51e392450fec8d7fca1554251a47
|
7
|
+
data.tar.gz: 6ee2318990b5e0a67579a8114f5e9f1a5b8215b21b5f6110c717c3b154db4b5ae08a962628b057a97124eaa043ce9d5f0ce6f2cda80fd715b8c7b9c2311fecc0
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
-
# slacken
|
1
|
+
# slacken [](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 `
|
10
|
+
Sample input source and output texts are in `examples/`.
|
9
11
|
|
10
12
|
```
|
11
13
|
> require 'slacken'
|
12
|
-
> puts Slacken.translate(File.read('
|
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*.
|
data/{sample → examples}/out.txt
RENAMED
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
53
|
-
.
|
54
|
-
|
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))
|
@@ -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
|