ascii_tree 1.0.0
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 +7 -0
- data/README.md +75 -0
- data/lib/ascii_tree.rb +15 -0
- data/lib/ascii_tree/base.rb +10 -0
- data/lib/ascii_tree/comment_stripper.rb +9 -0
- data/lib/ascii_tree/coordinate.rb +13 -0
- data/lib/ascii_tree/edge.rb +19 -0
- data/lib/ascii_tree/edge_parser.rb +42 -0
- data/lib/ascii_tree/node.rb +21 -0
- data/lib/ascii_tree/node_builder.rb +50 -0
- data/lib/ascii_tree/parenthesis_toggle.rb +38 -0
- data/lib/ascii_tree/relationship.rb +19 -0
- data/lib/ascii_tree/relationships_builder.rb +68 -0
- data/lib/ascii_tree/scanner.rb +35 -0
- data/lib/ascii_tree/word.rb +35 -0
- data/lib/ascii_tree/word_parser.rb +90 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: edd4a091f0389f3142009d7f90e2f92e28bda572
|
4
|
+
data.tar.gz: afb677ecd9caa5908a140a1a80d359d3c92bd790
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d3955dee1d7ae9b8478a464b933520b0a6b665a3c66c45ef3cadd1295c16e09a65108ba9e2bcaf88433fac05df014e99885527bbcfe4c6510929f78e5e1d6f87
|
7
|
+
data.tar.gz: 2570e3503e783e753716ee586b476367963250e93a75e32ac2196807ed5da5eb03b49958ea147eca13ce654401b03149aaec2a39d68e98efef9fff9813139929
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
## Ascii Tree
|
2
|
+
|
3
|
+
Parses a usable tree from ASCII art.
|
4
|
+
|
5
|
+
Ascii Tree turns something humans understand into something computers
|
6
|
+
understand. It is an expressive and efficient way to define trees.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
root = AsciiTree.parse('
|
12
|
+
|
13
|
+
# Christmas themed tree:
|
14
|
+
|
15
|
+
chestnuts
|
16
|
+
/ | \
|
17
|
+
roasting on an
|
18
|
+
/ \ \
|
19
|
+
open fire jack
|
20
|
+
/ \
|
21
|
+
frost nipping
|
22
|
+
/ | \
|
23
|
+
on your nose # Ouch!
|
24
|
+
|
25
|
+
')
|
26
|
+
|
27
|
+
root.id
|
28
|
+
#=> "chestnuts"
|
29
|
+
|
30
|
+
root.parent
|
31
|
+
#=> nil
|
32
|
+
|
33
|
+
root.children
|
34
|
+
#=> [#<AsciiTree::Node @id="roasting">, ...]
|
35
|
+
```
|
36
|
+
|
37
|
+
## Multiple words
|
38
|
+
|
39
|
+
Use parenthesis to group words into a single node:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
root = AsciiTree.parse('
|
43
|
+
|
44
|
+
(this is a single node)
|
45
|
+
/ | | | \
|
46
|
+
(so is this) but these are separate
|
47
|
+
|
48
|
+
')
|
49
|
+
|
50
|
+
root.id
|
51
|
+
#=> "this is a single node"
|
52
|
+
```
|
53
|
+
|
54
|
+
## Values
|
55
|
+
|
56
|
+
You can set arbitrary values on nodes:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
root = AsciiTree.parse('
|
60
|
+
|
61
|
+
root{123}
|
62
|
+
/ \
|
63
|
+
a{"foo"} b
|
64
|
+
/ \
|
65
|
+
c d{[1,2,3].reverse}
|
66
|
+
|
67
|
+
')
|
68
|
+
|
69
|
+
root.value
|
70
|
+
#=> 123
|
71
|
+
```
|
72
|
+
|
73
|
+
## Contribution
|
74
|
+
|
75
|
+
If you'd like to contribute, please open an issue or submit a pull request.
|
data/lib/ascii_tree.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "ascii_tree/base"
|
2
|
+
|
3
|
+
require "ascii_tree/coordinate"
|
4
|
+
require "ascii_tree/parenthesis_toggle"
|
5
|
+
require "ascii_tree/word"
|
6
|
+
require "ascii_tree/edge"
|
7
|
+
require "ascii_tree/relationship"
|
8
|
+
require "ascii_tree/node"
|
9
|
+
|
10
|
+
require "ascii_tree/comment_stripper"
|
11
|
+
require "ascii_tree/edge_parser"
|
12
|
+
require "ascii_tree/word_parser"
|
13
|
+
require "ascii_tree/scanner"
|
14
|
+
require "ascii_tree/relationships_builder"
|
15
|
+
require "ascii_tree/node_builder"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
def self.parse(string)
|
3
|
+
string = CommentStripper.strip(string)
|
4
|
+
words = WordParser.parse(string)
|
5
|
+
edges = EdgeParser.parse(string)
|
6
|
+
relationships = RelationshipsBuilder.build(words, edges)
|
7
|
+
|
8
|
+
NodeBuilder.build(relationships)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class AsciiTree::Edge
|
2
|
+
|
3
|
+
attr_reader :character, :coordinate, :parent_coordinate, :child_coordinate
|
4
|
+
|
5
|
+
def initialize(character:, coordinate:, parent_coordinate:, child_coordinate:)
|
6
|
+
@character = character
|
7
|
+
@coordinate = coordinate
|
8
|
+
@parent_coordinate = parent_coordinate
|
9
|
+
@child_coordinate = child_coordinate
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
character == other.character &&
|
14
|
+
coordinate == other.coordinate &&
|
15
|
+
parent_coordinate == other.parent_coordinate &&
|
16
|
+
child_coordinate == other.child_coordinate
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
module EdgeParser
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def parse(string)
|
6
|
+
edge_chars_with_coordinates(string).map do |char, coordinate|
|
7
|
+
offsets = edge_offsets[char]
|
8
|
+
|
9
|
+
Edge.new(
|
10
|
+
character: char,
|
11
|
+
coordinate: coordinate,
|
12
|
+
parent_coordinate: Coordinate.new(
|
13
|
+
x: coordinate.x + offsets[:parent][:x],
|
14
|
+
y: coordinate.y + offsets[:parent][:y]
|
15
|
+
),
|
16
|
+
child_coordinate: Coordinate.new(
|
17
|
+
x: coordinate.x + offsets[:child][:x],
|
18
|
+
y: coordinate.y + offsets[:child][:y]
|
19
|
+
)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def edge_offsets
|
27
|
+
{
|
28
|
+
"/" => { parent: { x: +1, y: -1 }, child: { x: -1, y: +1 } },
|
29
|
+
"|" => { parent: { x: 0, y: -1 }, child: { x: 0, y: +1 } },
|
30
|
+
"\\" => { parent: { x: -1, y: -1 }, child: { x: +1, y: +1 } }
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def edge_chars_with_coordinates(string)
|
35
|
+
Scanner.scan(string).select do |char, _|
|
36
|
+
edge_offsets.keys.include?(char)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
class Node
|
3
|
+
|
4
|
+
attr_reader :id, :value, :parent, :children
|
5
|
+
|
6
|
+
def initialize(id:, value:, parent:, children:)
|
7
|
+
@id = id
|
8
|
+
@value = value
|
9
|
+
@parent = parent
|
10
|
+
@children = children
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
id == other.id &&
|
15
|
+
value == other.value &&
|
16
|
+
parent == other.parent &&
|
17
|
+
children == other.children
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
class NodeBuilder
|
3
|
+
|
4
|
+
def self.build(*args)
|
5
|
+
new(*args).build
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(relationships)
|
9
|
+
@relationships = relationships
|
10
|
+
end
|
11
|
+
|
12
|
+
def build
|
13
|
+
build_for(root_word, nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :relationships
|
19
|
+
|
20
|
+
def root_word
|
21
|
+
relationships.first.parent_word
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_for(word, parent)
|
25
|
+
node = Node.new(
|
26
|
+
id: word.id,
|
27
|
+
value: word.value,
|
28
|
+
parent: parent,
|
29
|
+
children: []
|
30
|
+
)
|
31
|
+
|
32
|
+
children_for(word, node).each do |child|
|
33
|
+
node.children << child
|
34
|
+
end
|
35
|
+
|
36
|
+
node
|
37
|
+
end
|
38
|
+
|
39
|
+
def children_for(word, parent)
|
40
|
+
child_relationships = relationships.select do |r|
|
41
|
+
r.parent_word == word
|
42
|
+
end
|
43
|
+
|
44
|
+
child_relationships.map do |r|
|
45
|
+
build_for(r.child_word, parent)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
class ParenthesisToggle
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@on, @count = false, 0
|
6
|
+
end
|
7
|
+
|
8
|
+
def read(char)
|
9
|
+
if char == "("
|
10
|
+
@on = true
|
11
|
+
elsif char == ")"
|
12
|
+
@on = false
|
13
|
+
end
|
14
|
+
|
15
|
+
if char == "("
|
16
|
+
@count += 1
|
17
|
+
elsif char == ")"
|
18
|
+
@count -= 1
|
19
|
+
@count = 0 if @count < 0
|
20
|
+
end
|
21
|
+
|
22
|
+
@on = @count > 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def on?
|
26
|
+
@on
|
27
|
+
end
|
28
|
+
|
29
|
+
def off?
|
30
|
+
!@on
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :on
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
class Relationship
|
3
|
+
|
4
|
+
attr_reader :parent_word, :edge, :child_word
|
5
|
+
|
6
|
+
def initialize(parent_word:, edge:, child_word:)
|
7
|
+
@parent_word = parent_word
|
8
|
+
@edge = edge
|
9
|
+
@child_word = child_word
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
parent_word == other.parent_word &&
|
14
|
+
edge == other.edge &&
|
15
|
+
child_word == other.child_word
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
module RelationshipsBuilder
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def build(words, edges)
|
6
|
+
relationships = edges.map do |edge|
|
7
|
+
parent = words.detect { |w| w.include?(edge.parent_coordinate) }
|
8
|
+
child = words.detect { |w| w.include?(edge.child_coordinate) }
|
9
|
+
|
10
|
+
validate_presence(parent, child, edge)
|
11
|
+
|
12
|
+
Relationship.new(parent_word: parent, edge: edge, child_word: child)
|
13
|
+
end
|
14
|
+
|
15
|
+
validate_one_parent(relationships)
|
16
|
+
relationships
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate_presence(parent, child, edge)
|
22
|
+
if parent.nil? && child.nil?
|
23
|
+
error = "No parent or child"
|
24
|
+
elsif parent.nil?
|
25
|
+
error = "No parent for child '#{child.id}'"
|
26
|
+
elsif child.nil?
|
27
|
+
error = "No child for parent '#{parent.id}'"
|
28
|
+
end
|
29
|
+
|
30
|
+
if error
|
31
|
+
c = edge.coordinate
|
32
|
+
error += " for edge '#{edge.character}' at line #{c.y}, column #{c.x}"
|
33
|
+
raise ::AsciiTree::RelationshipError, error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_one_parent(relationships)
|
38
|
+
multiple_parents = relationships.select do |relationship|
|
39
|
+
count = relationships.count do |r|
|
40
|
+
r.child_word == relationship.child_word
|
41
|
+
end
|
42
|
+
|
43
|
+
count > 1
|
44
|
+
end
|
45
|
+
|
46
|
+
groups = multiple_parents.group_by { |r| r.child_word }
|
47
|
+
|
48
|
+
maps = groups.map do |child_word, relationships|
|
49
|
+
parent_words = relationships.map(&:parent_word)
|
50
|
+
[child_word.id, parent_words.map(&:id)]
|
51
|
+
end
|
52
|
+
|
53
|
+
if maps.any?
|
54
|
+
error = ""
|
55
|
+
|
56
|
+
maps.each do |child_word, parent_words|
|
57
|
+
parents = parent_words.join("' and '")
|
58
|
+
error += "'#{child_word}' has more than one parent: '#{parents}'.\n"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
raise ::AsciiTree::RelationshipError, error if error
|
63
|
+
end
|
64
|
+
|
65
|
+
class ::AsciiTree::RelationshipError < StandardError; end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
module Scanner
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def scan(string)
|
6
|
+
Enumerator.new do |yielder|
|
7
|
+
indexed_lines(string).each do |line, y|
|
8
|
+
indexed_chars(line).each do |char, x|
|
9
|
+
yielder.yield [char, Coordinate.new(x: x, y: y)]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def indexed_lines(string)
|
18
|
+
lines(string).each.with_index.to_a
|
19
|
+
end
|
20
|
+
|
21
|
+
def lines(string)
|
22
|
+
string.split("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def indexed_chars(line)
|
26
|
+
chars(line).each_with_index.to_a
|
27
|
+
end
|
28
|
+
|
29
|
+
def chars(line)
|
30
|
+
line.split("")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
class Word
|
3
|
+
|
4
|
+
attr_reader :id, :value, :start_coordinate, :end_coordinate
|
5
|
+
|
6
|
+
def initialize(id:, value:, start_coordinate:, end_coordinate:)
|
7
|
+
@id = id
|
8
|
+
@value = value
|
9
|
+
@start_coordinate = start_coordinate
|
10
|
+
@end_coordinate = end_coordinate
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
id == other.id &&
|
15
|
+
value == other.value &&
|
16
|
+
start_coordinate == other.start_coordinate &&
|
17
|
+
end_coordinate == other.end_coordinate
|
18
|
+
end
|
19
|
+
|
20
|
+
def include?(coordinate)
|
21
|
+
same_line?(coordinate.y) && inside?(coordinate.x)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def same_line?(y)
|
27
|
+
y == start_coordinate.y && y == end_coordinate.y
|
28
|
+
end
|
29
|
+
|
30
|
+
def inside?(x)
|
31
|
+
(start_coordinate.x..end_coordinate.x).include?(x)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module AsciiTree
|
2
|
+
module WordParser
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def parse(string)
|
6
|
+
chars = word_chars_with_coordinates(string)
|
7
|
+
group_contiguous(chars).map do |word_with_coords|
|
8
|
+
id, value = id_value(word_with_coords)
|
9
|
+
|
10
|
+
Word.new(
|
11
|
+
id: id,
|
12
|
+
value: value,
|
13
|
+
start_coordinate: word_with_coords.first.last,
|
14
|
+
end_coordinate: word_with_coords.last.last
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def word_chars_with_coordinates(string)
|
22
|
+
toggle = ParenthesisToggle.new
|
23
|
+
|
24
|
+
Scanner.scan(string).reject do |char, coordinate|
|
25
|
+
toggle.read(char)
|
26
|
+
toggle.off? && (edge?(char) || whitespace?(char))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def edge?(char)
|
31
|
+
["/", "|", "\\"].include?(char)
|
32
|
+
end
|
33
|
+
|
34
|
+
def whitespace?(char)
|
35
|
+
char.match(/\s/)
|
36
|
+
end
|
37
|
+
|
38
|
+
def id_value(word_with_coords)
|
39
|
+
chars = word_with_coords.map(&:first)
|
40
|
+
chars = remove_parentheses(chars)
|
41
|
+
|
42
|
+
word = chars.join
|
43
|
+
|
44
|
+
if word.end_with?("}")
|
45
|
+
id, tail = word.split("{", 2)
|
46
|
+
expression = tail[0..-2]
|
47
|
+
value = eval(expression)
|
48
|
+
[id, value]
|
49
|
+
else
|
50
|
+
[word, nil]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def remove_parentheses(chars)
|
55
|
+
if chars.first == "(" && chars.last == ")"
|
56
|
+
chars[1..-2]
|
57
|
+
else
|
58
|
+
chars
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def group_contiguous(chars)
|
63
|
+
chars.inject([]) do |array, (char, coord)|
|
64
|
+
prev = previous_coordinate(array)
|
65
|
+
|
66
|
+
if contigous?(prev, coord)
|
67
|
+
array.last << [char, coord]
|
68
|
+
else
|
69
|
+
array << [[char, coord]]
|
70
|
+
end
|
71
|
+
|
72
|
+
array
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def previous_coordinate(array)
|
77
|
+
previous_array = array.last
|
78
|
+
previous_tuple = previous_array.last if previous_array
|
79
|
+
previous_tuple.last if previous_tuple
|
80
|
+
end
|
81
|
+
|
82
|
+
def contigous?(previous_coordinate, coordinate)
|
83
|
+
previous_coordinate &&
|
84
|
+
previous_coordinate.y == coordinate.y &&
|
85
|
+
previous_coordinate.x == coordinate.x - 1
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ascii_tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Patuzzo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
description: Parses a usable tree from ASCII art.
|
28
|
+
email: chris@patuzzo.co.uk
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- README.md
|
34
|
+
- lib/ascii_tree.rb
|
35
|
+
- lib/ascii_tree/base.rb
|
36
|
+
- lib/ascii_tree/comment_stripper.rb
|
37
|
+
- lib/ascii_tree/coordinate.rb
|
38
|
+
- lib/ascii_tree/edge.rb
|
39
|
+
- lib/ascii_tree/edge_parser.rb
|
40
|
+
- lib/ascii_tree/node.rb
|
41
|
+
- lib/ascii_tree/node_builder.rb
|
42
|
+
- lib/ascii_tree/parenthesis_toggle.rb
|
43
|
+
- lib/ascii_tree/relationship.rb
|
44
|
+
- lib/ascii_tree/relationships_builder.rb
|
45
|
+
- lib/ascii_tree/scanner.rb
|
46
|
+
- lib/ascii_tree/word.rb
|
47
|
+
- lib/ascii_tree/word_parser.rb
|
48
|
+
homepage: https://github.com/tuzz/ascii_tree
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
metadata: {}
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.2.2
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Ascii Tree
|
72
|
+
test_files: []
|