slacken 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![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 `
|
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
|