qiita_scouter 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +8 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.md +80 -0
- data/Rakefile +8 -0
- data/bin/qiita_scouter +35 -0
- data/lib/models/article.rb +11 -0
- data/lib/models/articles.rb +30 -0
- data/lib/models/user.rb +12 -0
- data/lib/qiita_items_json_loader.rb +43 -0
- data/lib/qiita_scouter/version.rb +4 -0
- data/lib/qiita_scouter_calculator.rb +42 -0
- data/lib/qiita_scouter_core.rb +43 -0
- data/lib/qiita_user_json_loader.rb +29 -0
- data/qiita_scouter.gemspec +27 -0
- data/spec/models/article_spec.rb +57 -0
- data/spec/models/articles_spec.rb +281 -0
- data/spec/models/user_spec.rb +55 -0
- data/spec/qiita_items_json_loader_spec.rb +59 -0
- data/spec/qiita_scouter_calculator_spec.rb +188 -0
- data/spec/qiita_user_json_loader_spec.rb +66 -0
- data/spec/spec_helper.rb +16 -0
- metadata +160 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'qiita_scouter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'qiita_scouter'
|
8
|
+
spec.version = QiitaScouter::VERSION
|
9
|
+
spec.authors = ['tbpgr']
|
10
|
+
spec.email = ['tbpgr@tbpgr.jp']
|
11
|
+
spec.summary = 'Analyze your qiita power'
|
12
|
+
spec.description = 'Analyze your qiita power'
|
13
|
+
spec.homepage = 'https://github.com/tbpgr/qiita_scouter'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'thor', '~> 0.18.1'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'rspec', '~> 2.14', '>= 2.14.1'
|
24
|
+
spec.add_development_dependency 'simplecov', '~> 0.8', '>= 0.8.2'
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
26
|
+
spec.add_development_dependency 'rake', '~> 0'
|
27
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'models/article'
|
4
|
+
|
5
|
+
# rubocop:disable LineLength, UnusedMethodArgument
|
6
|
+
describe QiitaScouter::Article do
|
7
|
+
context :initialize do
|
8
|
+
cases = [
|
9
|
+
{
|
10
|
+
case_no: 1,
|
11
|
+
case_title: 'valid case',
|
12
|
+
init: {
|
13
|
+
'tags' => [
|
14
|
+
{
|
15
|
+
'name' => 'Ruby',
|
16
|
+
'url_name' => 'ruby',
|
17
|
+
'versions' => []
|
18
|
+
}
|
19
|
+
],
|
20
|
+
'stock_count' => 2
|
21
|
+
},
|
22
|
+
expected_tags: ['Ruby'],
|
23
|
+
expected_stock_count: 2
|
24
|
+
}
|
25
|
+
]
|
26
|
+
|
27
|
+
cases.each do |c|
|
28
|
+
it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
|
29
|
+
begin
|
30
|
+
case_before c
|
31
|
+
|
32
|
+
# -- given --
|
33
|
+
article = QiitaScouter::Article.new(c[:init])
|
34
|
+
|
35
|
+
# -- when --
|
36
|
+
actual_tags = article.tags
|
37
|
+
actual_stock_count = article.stock_count
|
38
|
+
|
39
|
+
# -- then --
|
40
|
+
expect(actual_tags).to eq(c[:expected_tags])
|
41
|
+
expect(actual_stock_count).to eq(c[:expected_stock_count])
|
42
|
+
ensure
|
43
|
+
case_after c
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def case_before(_c)
|
48
|
+
# implement each case before
|
49
|
+
end
|
50
|
+
|
51
|
+
def case_after(_c)
|
52
|
+
# implement each case after
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
# rubocop:enable LineLength, UnusedMethodArgument
|
@@ -0,0 +1,281 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'models/article'
|
4
|
+
require 'models/articles'
|
5
|
+
|
6
|
+
# rubocop:disable LineLength, UnusedMethodArgument
|
7
|
+
describe QiitaScouter::Articles do
|
8
|
+
context :<< do
|
9
|
+
cases = [
|
10
|
+
{
|
11
|
+
case_no: 1,
|
12
|
+
case_title: '2 list case',
|
13
|
+
appends: [
|
14
|
+
QiitaScouter::Article.new(
|
15
|
+
'tags' => [
|
16
|
+
{ 'name' => 'Ruby' },
|
17
|
+
{ 'name' => 'Graphviz' }
|
18
|
+
],
|
19
|
+
'stock_count' => 25
|
20
|
+
),
|
21
|
+
QiitaScouter::Article.new(
|
22
|
+
'tags' => [
|
23
|
+
{ 'name' => 'Ruby' },
|
24
|
+
{ 'name' => 'Graphviz' }
|
25
|
+
],
|
26
|
+
'stock_count' => 25
|
27
|
+
)
|
28
|
+
],
|
29
|
+
expected: 2
|
30
|
+
},
|
31
|
+
{
|
32
|
+
case_no: 2,
|
33
|
+
case_title: '1 list case',
|
34
|
+
appends: [
|
35
|
+
QiitaScouter::Article.new(
|
36
|
+
'tags' => [
|
37
|
+
{ 'name' => 'Ruby' },
|
38
|
+
{ 'name' => 'Graphviz' }
|
39
|
+
],
|
40
|
+
'stock_count' => 25
|
41
|
+
)
|
42
|
+
],
|
43
|
+
expected: 1
|
44
|
+
}
|
45
|
+
]
|
46
|
+
|
47
|
+
cases.each do |c|
|
48
|
+
it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
|
49
|
+
begin
|
50
|
+
case_before c
|
51
|
+
|
52
|
+
# -- given --
|
53
|
+
qiita_scouter_articles = QiitaScouter::Articles.new
|
54
|
+
|
55
|
+
# -- when --
|
56
|
+
c[:appends].each { |e|qiita_scouter_articles << e }
|
57
|
+
actual = qiita_scouter_articles.size
|
58
|
+
|
59
|
+
# -- then --
|
60
|
+
expect(actual).to eq(c[:expected])
|
61
|
+
ensure
|
62
|
+
case_after c
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def case_before(_c)
|
67
|
+
# implement each case before
|
68
|
+
end
|
69
|
+
|
70
|
+
def case_after(_c)
|
71
|
+
# implement each case after
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context :+ do
|
77
|
+
cases = [
|
78
|
+
{
|
79
|
+
case_no: 1,
|
80
|
+
case_title: 'init 0 append 2 list case',
|
81
|
+
init: [],
|
82
|
+
appends: QiitaScouter::Articles.new([
|
83
|
+
QiitaScouter::Article.new(
|
84
|
+
'tags' => [
|
85
|
+
{ 'name' => 'Ruby' },
|
86
|
+
{ 'name' => 'Graphviz' }
|
87
|
+
],
|
88
|
+
'stock_count' => 25
|
89
|
+
),
|
90
|
+
QiitaScouter::Article.new(
|
91
|
+
'tags' => [
|
92
|
+
{ 'name' => 'PHP' },
|
93
|
+
{ 'name' => 'Graphviz' }
|
94
|
+
],
|
95
|
+
'stock_count' => 32
|
96
|
+
)
|
97
|
+
]),
|
98
|
+
expected: 2
|
99
|
+
},
|
100
|
+
{
|
101
|
+
case_no: 2,
|
102
|
+
case_title: 'init 2 append 2 list case',
|
103
|
+
init: [
|
104
|
+
QiitaScouter::Article.new(
|
105
|
+
'tags' => [
|
106
|
+
{ 'name' => 'Ruby' },
|
107
|
+
{ 'name' => 'Graphviz' }
|
108
|
+
],
|
109
|
+
'stock_count' => 25
|
110
|
+
),
|
111
|
+
QiitaScouter::Article.new(
|
112
|
+
'tags' => [
|
113
|
+
{ 'name' => 'Ruby' },
|
114
|
+
{ 'name' => 'Graphviz' }
|
115
|
+
],
|
116
|
+
'stock_count' => 25
|
117
|
+
)
|
118
|
+
],
|
119
|
+
appends: QiitaScouter::Articles.new([
|
120
|
+
QiitaScouter::Article.new(
|
121
|
+
'tags' => [
|
122
|
+
{ 'name' => 'Ruby' },
|
123
|
+
{ 'name' => 'Graphviz' }
|
124
|
+
],
|
125
|
+
'stock_count' => 25
|
126
|
+
),
|
127
|
+
QiitaScouter::Article.new(
|
128
|
+
'tags' => [
|
129
|
+
{ 'name' => 'PHP' },
|
130
|
+
{ 'name' => 'Graphviz' }
|
131
|
+
],
|
132
|
+
'stock_count' => 32
|
133
|
+
)
|
134
|
+
]),
|
135
|
+
expected: 4
|
136
|
+
}
|
137
|
+
]
|
138
|
+
|
139
|
+
cases.each do |c|
|
140
|
+
it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
|
141
|
+
begin
|
142
|
+
case_before c
|
143
|
+
|
144
|
+
# -- given --
|
145
|
+
qiita_scouter_articles = QiitaScouter::Articles.new(c[:init])
|
146
|
+
|
147
|
+
# -- when --
|
148
|
+
qiita_scouter_articles += c[:appends]
|
149
|
+
actual = qiita_scouter_articles.size
|
150
|
+
|
151
|
+
# -- then --
|
152
|
+
expect(actual).to eq(c[:expected])
|
153
|
+
ensure
|
154
|
+
case_after c
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def case_before(_c)
|
159
|
+
# implement each case before
|
160
|
+
end
|
161
|
+
|
162
|
+
def case_after(_c)
|
163
|
+
# implement each case after
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context :size do
|
169
|
+
cases = [
|
170
|
+
{
|
171
|
+
case_no: 1,
|
172
|
+
case_title: '2 list case',
|
173
|
+
articles: [
|
174
|
+
QiitaScouter::Article.new(
|
175
|
+
'tags' => [
|
176
|
+
{ 'name' => 'Ruby' },
|
177
|
+
{ 'name' => 'Graphviz' }
|
178
|
+
],
|
179
|
+
'stock_count' => 25
|
180
|
+
),
|
181
|
+
QiitaScouter::Article.new(
|
182
|
+
'tags' => [
|
183
|
+
{ 'name' => 'Ruby' },
|
184
|
+
{ 'name' => 'Graphviz' }
|
185
|
+
],
|
186
|
+
'stock_count' => 25
|
187
|
+
)
|
188
|
+
],
|
189
|
+
expected: 2
|
190
|
+
},
|
191
|
+
{
|
192
|
+
case_no: 2,
|
193
|
+
case_title: 'empty list case',
|
194
|
+
articles: [],
|
195
|
+
expected: 0
|
196
|
+
}
|
197
|
+
]
|
198
|
+
|
199
|
+
cases.each do |c|
|
200
|
+
it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
|
201
|
+
begin
|
202
|
+
case_before c
|
203
|
+
|
204
|
+
# -- given --
|
205
|
+
qiita_scouter_articles = QiitaScouter::Articles.new(c[:articles])
|
206
|
+
|
207
|
+
# -- when --
|
208
|
+
actual = qiita_scouter_articles.size
|
209
|
+
|
210
|
+
# -- then --
|
211
|
+
expect(actual).to eq(c[:expected])
|
212
|
+
ensure
|
213
|
+
case_after c
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def case_before(_c)
|
218
|
+
# implement each case before
|
219
|
+
end
|
220
|
+
|
221
|
+
def case_after(_c)
|
222
|
+
# implement each case after
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context :each do
|
228
|
+
cases = [
|
229
|
+
{
|
230
|
+
case_no: 1,
|
231
|
+
case_title: '2 list case',
|
232
|
+
articles: [
|
233
|
+
QiitaScouter::Article.new(
|
234
|
+
'tags' => [
|
235
|
+
{ 'name' => 'Ruby' },
|
236
|
+
{ 'name' => 'Graphviz' }
|
237
|
+
],
|
238
|
+
'stock_count' => 1
|
239
|
+
),
|
240
|
+
QiitaScouter::Article.new(
|
241
|
+
'tags' => [
|
242
|
+
{ 'name' => 'Ruby' },
|
243
|
+
{ 'name' => 'Graphviz' }
|
244
|
+
],
|
245
|
+
'stock_count' => 2
|
246
|
+
)
|
247
|
+
],
|
248
|
+
expected: 3
|
249
|
+
}
|
250
|
+
]
|
251
|
+
|
252
|
+
cases.each do |c|
|
253
|
+
it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
|
254
|
+
begin
|
255
|
+
case_before c
|
256
|
+
|
257
|
+
# -- given --
|
258
|
+
qiita_scouter_articles = QiitaScouter::Articles.new(c[:articles])
|
259
|
+
|
260
|
+
# -- when --
|
261
|
+
total = 0
|
262
|
+
qiita_scouter_articles.each { |e|total += e.stock_count }
|
263
|
+
|
264
|
+
# -- then --
|
265
|
+
expect(total).to eq(c[:expected])
|
266
|
+
ensure
|
267
|
+
case_after c
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def case_before(_c)
|
272
|
+
# implement each case before
|
273
|
+
end
|
274
|
+
|
275
|
+
def case_after(_c)
|
276
|
+
# implement each case after
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
# rubocop:enable LineLength, UnusedMethodArgument
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'models/user'
|
4
|
+
|
5
|
+
# rubocop:disable LineLength, UnusedMethodArgument
|
6
|
+
describe QiitaScouter::User do
|
7
|
+
context :initialize do
|
8
|
+
cases = [
|
9
|
+
{
|
10
|
+
case_no: 1,
|
11
|
+
case_title: 'valid case',
|
12
|
+
init: {
|
13
|
+
'url_name' => 'tbpgr',
|
14
|
+
'followers' => 10,
|
15
|
+
'items' => 20
|
16
|
+
},
|
17
|
+
expected_url_name: 'tbpgr',
|
18
|
+
expected_followers: 10,
|
19
|
+
expected_items: 20
|
20
|
+
}
|
21
|
+
]
|
22
|
+
|
23
|
+
cases.each do |c|
|
24
|
+
it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
|
25
|
+
begin
|
26
|
+
case_before c
|
27
|
+
|
28
|
+
# -- given --
|
29
|
+
article = QiitaScouter::User.new(c[:init])
|
30
|
+
|
31
|
+
# -- when --
|
32
|
+
actual_url_name = article.url_name
|
33
|
+
actual_followers = article.followers
|
34
|
+
actual_items = article.items
|
35
|
+
|
36
|
+
# -- then --
|
37
|
+
expect(actual_url_name).to eq(c[:expected_url_name])
|
38
|
+
expect(actual_followers).to eq(c[:expected_followers])
|
39
|
+
expect(actual_items).to eq(c[:expected_items])
|
40
|
+
ensure
|
41
|
+
case_after c
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def case_before(_c)
|
46
|
+
# implement each case before
|
47
|
+
end
|
48
|
+
|
49
|
+
def case_after(_c)
|
50
|
+
# implement each case after
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
# rubocop:enable LineLength, UnusedMethodArgument
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'qiita_items_json_loader'
|
4
|
+
require 'json'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'openssl'
|
7
|
+
require 'models/article'
|
8
|
+
require 'models/articles'
|
9
|
+
|
10
|
+
# rubocop:disable LineLength, UnusedMethodArgument
|
11
|
+
describe QiitaScouter::QiitaItemsJsonLoader do
|
12
|
+
context :load do
|
13
|
+
cases = [
|
14
|
+
{
|
15
|
+
case_no: 1,
|
16
|
+
case_title: 'valid case',
|
17
|
+
user: 'user',
|
18
|
+
expected: 150
|
19
|
+
}
|
20
|
+
]
|
21
|
+
|
22
|
+
# DummyForItem
|
23
|
+
class DummyForItem
|
24
|
+
def read
|
25
|
+
'[{"id":133680,"uuid":"69c3d606f47b9d5a2a1e","user":{"id":43480,"url_name":"tbpgr","profile_image_url":"https://pbs.twimg.com/profile_images/378800000650620553/44cda07bda72f68147025c752716b580_normal.png"},"title":"Ruby | Collection Pipeline でランク集計を実装する","created_at":"2014-09-15 20:24:42 +0900","updated_at":"2014-09-15 20:24:42 +0900","created_at_in_words":"約17時間","updated_at_in_words":"約17時間","tags":[{"name":"Ruby","url_name":"ruby","icon_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/9de6a11d330f5694820082438f88ccf4a1b289b2/medium.jpg?1364837630","versions":[]}],"stock_count":2,"comment_count":0,"url":"http://qiita.com/tbpgr/items/69c3d606f47b9d5a2a1e","created_at_as_seconds":1410780282,"tweet":false,"gist_url":null,"private":false,"raw_body":"# Ruby | Collection Pipeline でランク集計を実装する\n\n## 概要\nCollection Pipeline でランク集計を実装します\n\nMartin Fowler 氏の下記ページを参考にしました。\n[Collection Pipeline](http://martinfowler.com/articles/collection-pipeline/)\n\n## サンプル\n### 仕様\nプログラム言語に関するアンケートを行ったとします。\n解答は言語名で記述されています。\n\n言語でグループ化して、言語ごとの投票数を集計し、\n\n* 言語別投票数の降順、言語名の昇順でソート\n* 結果を Markdown (GitHub Flavored Markdown) の表形式で出力する\n\n### 入力データ\n\n~~~\nRuby\nRuby\nRuby\nJava\nJavaScript\nC++\nRuby\nPython\nBash\nRuby\nRuby\nPerl\nC\nVB\nPHP\nJavaScript\nVB\nJava\nJavaScript\nRuby\nC\nPascal\nPHP\nJava\nPython\nRuby\nPython\nC++\nC\nJava\nJava\nPython\nMathematica\nJavaScript\nPHP\nJava\nC\nScala\nC\nC\n~~~\n\n### プログラム\n\n~~~ruby\nrequire \'tbpgr_utils\'\n\nlangs =\"Ruby\nRuby\nRuby\nJava\nJavaScript\nC++\nRuby\nPython\nBash\nRuby\nRuby\nPerl\nC\nVB\nPHP\nJavaScript\nVB\nJava\nJavaScript\nRuby\nC\nPascal\nPHP\nJava\nPython\nRuby\nPython\nC++\nC\nJava\nJava\nPython\nMathematica\nJavaScript\nPHP\nJava\nC\nScala\nC\nC\n\"\n\ndef group_ranked_list(ary)\n list = ary.group_by.with_index(1) { |e|e }\n .map { |k, v| {language: k, count: v.size} }\n .sort { |a, b|\n (-(a[:count] \u003c=\u003e b[:count])).nonzero? ||\n a[:language] \u003c=\u003e b[:language]\n }.map { |e|e.values }\n rank(list)\nend\n\ndef rank(ary)\n rank_count = 1\n [*0..(ary.size - 1)].each_with_object([]) do |i, memo|\n rank_count += 1 if i.nonzero? \u0026\u0026 (ary[i - 1].last != ary[i].last)\n memo \u003c\u003c [rank_count] + ary[i]\n end\nend\n\ndef headers\n \u003c\u003c-EOS\n|Rank|Language|Count|\n|:--|:--|:--|\n EOS\nend\n\nanswer_langs = langs.each_line.map(\u0026:chomp)\n\nprint headers + group_ranked_list(answer_langs).to_table(:left)\n\n__END__\nArray#to_table は TbpgrUtils gem (自作 gem) のメソッド。\n二次元配列を Markdown のテーブルフォーマットに変換します。\nTbpgrUtils は私の遊び場的ユーティリティ。実用性はない。\n~~~\n\n※多分もっと良い書き方があるでしょう。\n\n## 出力\n\n~~~markdown\n|Rank|Language|Count|\n|:--|:--|:--|\n|1|Ruby |8|\n|2|C |6|\n|2|Java |6|\n|3|JavaScript |4|\n|3|Python |4|\n|4|PHP |3|\n|5|C++ |2|\n|5|VB |2|\n|6|Bash |1|\n|6|Mathematica|1|\n|6|Pascal |1|\n|6|Perl |1|\n|6|Scala |1|\n~~~\n\n## 出力を実際に Markdown として Qiita に貼り付け\n\n|Rank|Language|Count|\n|:--|:--|:--|\n|1|Ruby |8|\n|2|C |6|\n|2|Java |6|\n|3|JavaScript |4|\n|3|Python |4|\n|4|PHP |3|\n|5|C++ |2|\n|5|VB |2|\n|6|Bash |1|\n|6|Mathematica|1|\n|6|Pascal |1|\n|6|Perl |1|\n|6|Scala |1|\n\n## 参照\n* Martin Fowler Collection Pipeline\n http://martinfowler.com/articles/collection-pipeline/\n\n* tbpgr_utils gem\n https://github.com/tbpgr/tbpgr_utils\n","body":"\u003ch1\u003e\n \u003cspan id=\"1-1\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#1-1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eRuby | Collection Pipeline でランク集計を実装する\n \u003c/h1\u003e\u003ch2\u003e\n \u003cspan id=\"2-1\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e概要\n \u003c/h2\u003e\u003cp\u003eCollection Pipeline でランク集計を実装します\u003c/p\u003e\n\u003cp\u003eMartin Fowler 氏の下記ページを参考にしました。\u003cbr\u003e\n\u003ca href=\"http://martinfowler.com/articles/collection-pipeline/\" title=\"\" target=\"_blank\"\u003eCollection Pipeline\u003c/a\u003e\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-2\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eサンプル\n \u003c/h2\u003e\u003ch3\u003e\n \u003cspan id=\"3-1\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#3-1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e仕様\n \u003c/h3\u003e\u003cp\u003eプログラム言語に関するアンケートを行ったとします。\u003cbr\u003e\n解答は言語名で記述されています。\u003c/p\u003e\n\u003cp\u003e言語でグループ化して、言語ごとの投票数を集計し、\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003e言語別投票数の降順、言語名の昇順でソート\u003c/li\u003e\n\u003cli\u003e結果を Markdown (GitHub Flavored Markdown) の表形式で出力する\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3\u003e\n \u003cspan id=\"3-2\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#3-2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e入力データ\n \u003c/h3\u003e\u003cdiv class=\"code-frame\" data-lang=\"text\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003eRuby\nRuby\nRuby\nJava\nJavaScript\nC++\nRuby\nPython\nBash\nRuby\nRuby\nPerl\nC\nVB\nPHP\nJavaScript\nVB\nJava\nJavaScript\nRuby\nC\nPascal\nPHP\nJava\nPython\nRuby\nPython\nC++\nC\nJava\nJava\nPython\nMathematica\nJavaScript\nPHP\nJava\nC\nScala\nC\nC\n\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\u003ch3\u003e\n \u003cspan id=\"3-3\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#3-3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eプログラム\n \u003c/h3\u003e\u003cdiv class=\"code-frame\" data-lang=\"ruby\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003cspan class=\"nb\"\u003erequire\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;tbpgr_utils\u0026#39;\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003elangs\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026quot;Ruby\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eRuby\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eRuby\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJava\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJavaScript\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eC++\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eRuby\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePython\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eBash\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eRuby\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eRuby\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePerl\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eC\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eVB\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePHP\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJavaScript\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eVB\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJava\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJavaScript\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eRuby\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eC\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePascal\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePHP\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJava\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePython\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eRuby\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePython\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eC++\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eC\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJava\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJava\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePython\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eMathematica\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJavaScript\u003c/span\u003e\n\u003cspan class=\"s2\"\u003ePHP\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eJava\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eC\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eScala\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eC\u003c/span\u003e\n\u003cspan class=\"s2\"\u003eC\u003c/span\u003e\n\u003cspan class=\"s2\"\u003e\u0026quot;\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003egroup_ranked_list\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eary\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n \u003cspan class=\"n\"\u003elist\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eary\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egroup_by\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewith_index\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"ss\"\u003elanguage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esort\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:count\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;=\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:count\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enonzero?\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e\n \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:language\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;=\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eb\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:language\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evalues\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"n\"\u003erank\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elist\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003erank\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eary\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n \u003cspan class=\"n\"\u003erank_count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n \u003cspan class=\"o\"\u003e[*\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e.\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eary\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e].\u003c/span\u003e\u003cspan class=\"n\"\u003eeach_with_object\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ememo\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003erank_count\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enonzero?\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eary\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e].\u003c/span\u003e\u003cspan class=\"n\"\u003elast\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"n\"\u003eary\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e].\u003c/span\u003e\u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n \u003cspan class=\"n\"\u003ememo\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003erank_count\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eary\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eheaders\u003c/span\u003e\n \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;-\u003c/span\u003e\u003cspan class=\"no\"\u003eEOS\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e|Rank|Language|Count|\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e|:--|:--|:--|\u003c/span\u003e\n\u003cspan class=\"no\"\u003e EOS\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003eanswer_langs\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elangs\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eeach_line\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"ss\"\u003e:chomp\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003egroup_ranked_list\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eanswer_langs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eto_table\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003e:left\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"cp\"\u003e__END__\u003c/span\u003e\n\u003cspan class=\"cp\"\u003eArray#to_table は TbpgrUtils gem (自作 gem) のメソッド。\u003c/span\u003e\n\u003cspan class=\"cp\"\u003e二次元配列を Markdown のテーブルフォーマットに変換します。\u003c/span\u003e\n\u003cspan class=\"cp\"\u003eTbpgrUtils は私の遊び場的ユーティリティ。実用性はない。\u003c/span\u003e\n\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\u003cp\u003e※多分もっと良い書き方があるでしょう。\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-3\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e出力\n \u003c/h2\u003e\u003cdiv class=\"code-frame\" data-lang=\"markdown\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e|Rank|Language|Count|\n|:--|:--|:--|\n|1|Ruby |8|\n|2|C |6|\n|2|Java |6|\n|3|JavaScript |4|\n|3|Python |4|\n|4|PHP |3|\n|5|C++ |2|\n|5|VB |2|\n|6|Bash |1|\n|6|Mathematica|1|\n|6|Pascal |1|\n|6|Perl |1|\n|6|Scala |1|\n\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\u003ch2\u003e\n \u003cspan id=\"2-4\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-4\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e出力を実際に Markdown として Qiita に貼り付け\n \u003c/h2\u003e\n\u003ctable\u003e\u003cthead\u003e\n\u003ctr\u003e\n\u003cth style=\"text-align: left\"\u003eRank\u003c/th\u003e\n\u003cth style=\"text-align: left\"\u003eLanguage\u003c/th\u003e\n\u003cth style=\"text-align: left\"\u003eCount\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eRuby\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e8\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eC\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eJava\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e3\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eJavaScript\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e4\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e3\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003ePython\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e4\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e4\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003ePHP\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e3\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e5\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eC++\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e5\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eVB\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e2\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eBash\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eMathematica\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003ePascal\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003ePerl\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align: left\"\u003e6\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003eScala\u003c/td\u003e\n\u003ctd style=\"text-align: left\"\u003e1\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\u003c/table\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-5\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-5\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e参照\n \u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cp\u003eMartin Fowler Collection Pipeline\u003cbr\u003e\n\u003ca href=\"http://martinfowler.com/articles/collection-pipeline/\" title=\"http://martinfowler.com/articles/collection-pipeline/\" target=\"_blank\"\u003ehttp://martinfowler.com/articles/collection-pipeline/\u003c/a\u003e\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003etbpgr_utils gem\u003cbr\u003e\n\u003ca href=\"https://github.com/tbpgr/tbpgr_utils\" title=\"https://github.com/tbpgr/tbpgr_utils\" target=\"_blank\"\u003ehttps://github.com/tbpgr/tbpgr_utils\u003c/a\u003e\u003c/p\u003e\u003c/li\u003e\n\u003c/ul\u003e\n","stock_users":["morika-t@github","Reds"]},{"id":132750,"uuid":"7ba662f77b110e9ad0be","user":{"id":43480,"url_name":"tbpgr","profile_image_url":"https://pbs.twimg.com/profile_images/378800000650620553/44cda07bda72f68147025c752716b580_normal.png"},"title":"Ruby | Gvizでインデント構造のテキストをマインドマップに変換する","created_at":"2014-09-12 22:19:05 +0900","updated_at":"2014-09-15 13:09:26 +0900","created_at_in_words":"4日","updated_at_in_words":"1日","tags":[{"name":"Ruby","url_name":"ruby","icon_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/9de6a11d330f5694820082438f88ccf4a1b289b2/medium.jpg?1364837630","versions":[]},{"name":"Graphviz","url_name":"graphviz","icon_url":"/icons/medium/missing.png","versions":[]}],"stock_count":6,"comment_count":0,"url":"http://qiita.com/tbpgr/items/7ba662f77b110e9ad0be","created_at_as_seconds":1410527945,"tweet":false,"gist_url":null,"private":false,"raw_body":"# Ruby | Gvizでインデント構造のテキストをマインドマップに変換する\n\n## 概要\nGvizでインデント構造のテキストをマインドマップに変換します。\n\n## 利用ライブラリ\n利用ライブラリについては、下記記事参照\n\n[Gvizでインデント構造のテキストをグラフに変換する](http://qiita.com/tbpgr/items/770d56834103efe2b723)\n\n## サンプルコード\n\n~~~ruby\nrequire \'familyable\'\nrequire \'gottani_core\'\nrequire \"gviz\"\n\ntext =\u003c\u003c-EOS\nRuby\n スクリプト言語\n 影響\n From\n Perl\n Lisp\n Smalltalk\n To\n CoffeeScript\n Elixir\n Author\n まつもとゆきひろ\n Matz\n 松江\n Heroku\n Web\n Ruby on Rails\n Author\n DHH\n Products\n Redmine\n GitLab\n Sinatra\n DevOps\n Vagrant\n Author\n M.Hashimoto\n Provider\n Virtual Box\n VMWare\n Workstation\n Fusion\n AWS\n Chef\n Puppet\n 特徴\n 楽しい\n すべてがオブジェクト\n 可読性の高さ\n 動的言語\n 強力なメタプログラミング\n DSL作成の容易さ\nEOS\n\nsp2 = Gottani::Space2.new\nindent_text = sp2.space2_to_common(text)\n\nindexed_indent_text = indent_text.reverse.map.with_index { |e, i|e.merge({id: i}) }\n\nmodule Familyable\n class Person\n attr_accessor :value, :level\n end\nend\n\ni = 0\npersons = indexed_indent_text.reduce([]) do |persons, person|\n parent = indexed_indent_text[(i + 1)..-1]\n .find { |e|e[:level] == person[:level] - 1 }\n parent_ids = parent.nil? ? [] : [parent[:id]]\n person = Familyable::Person.new(id: i, parent_ids: parent_ids).tap do |e|\n e.value = person[:value]\n e.level = person[:level]\n end\n persons \u003c\u003c person\n i += 1\n persons\nend\n\nsorted_persons = persons.reverse\nfamily = Familyable::Family.new(family: persons)\n\nGraph do\n global layout:\'twopi\', overlap:false, splines: :curved\n nodes fontname:\'MS GOTHIC\', shape: :none, style:\'filled\'\n edges dir: :none\n # ノードの定義\n sorted_persons.each { |person|node :\"person#{person.id}\", { label: person.value } }\n\n # エッジの定義\n sorted_persons.each do |person|\n parents = family.get_parents(person)\n next if parents.empty?\n parent = parents.first\n options = {}\n options[:penwidth] = 6 if parent.level == 0\n edge :\"person#{parent.id}_person#{person.id}\", options\n\n end\n\n # *** 根のランク, スタイル定義 ***\n min_id = persons.find { |e|e.level == 0 }.id\n min_person_key = :\"person#{min_id}\"\n rank :min, [min_person_key]\n node min_person_key, { shape: \'pentagon\', fontsize: 80, fillcolor: :red }\n\n # *** 根以外のノードのランク定義 ***\n ranked_persons = sorted_persons.group_by { |e|e.level }.select { |key, value|key.nonzero? }\n ranked_persons.each do |rank, each_rank_persons|\n each_rank_persons.map { |e|node :\"person#{e.id}\", { fontsize: 40 } } if rank == 1\n persons = each_rank_persons.map { |e|\"person#{e.id}\".to_sym }\n rank(:same, persons)\n end\n\n save :\"givz_mindmap\", :png\nend\n~~~\n\n## 出力\n\n![givz_mindmap.png](https://qiita-image-store.s3.amazonaws.com/0/43480/b67aaead-ba36-cd32-3211-e89128d87404.png)\n\n## 参照\n* Gviz gem\n https://github.com/melborne/Gviz\n* tbpgr_utils gem\n https://github.com/tbpgr/tbpgr_utils\n* gottani gem\n https://github.com/tbpgr/gottani\n\n## 関連記事\n* [ryosy383様 Gvizでマインドマップを作成してみた](http://qiita.com/ryosy383/items/c1644031db7da38323bc)\n","body":"\u003ch1\u003e\n \u003cspan id=\"1-1\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#1-1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eRuby | Gvizでインデント構造のテキストをマインドマップに変換する\n \u003c/h1\u003e\u003ch2\u003e\n \u003cspan id=\"2-1\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e概要\n \u003c/h2\u003e\u003cp\u003eGvizでインデント構造のテキストをマインドマップに変換します。\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-2\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e利用ライブラリ\n \u003c/h2\u003e\u003cp\u003e利用ライブラリについては、下記記事参照\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://qiita.com/tbpgr/items/770d56834103efe2b723\" title=\"\"\u003eGvizでインデント構造のテキストをグラフに変換する\u003c/a\u003e\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-3\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eサンプルコード\n \u003c/h2\u003e\u003cdiv class=\"code-frame\" data-lang=\"ruby\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003cspan class=\"nb\"\u003erequire\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;familyable\u0026#39;\u003c/span\u003e\n\u003cspan class=\"nb\"\u003erequire\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;gottani_core\u0026#39;\u003c/span\u003e\n\u003cspan class=\"nb\"\u003erequire\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026quot;gviz\u0026quot;\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003etext\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026lt;\u0026lt;-\u003c/span\u003e\u003cspan class=\"no\"\u003eEOS\u003c/span\u003e\n\u003cspan class=\"sh\"\u003eRuby\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e スクリプト言語\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e 影響\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e From\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Perl\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Lisp\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Smalltalk\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e To\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e CoffeeScript\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Elixir\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Author\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e まつもとゆきひろ\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Matz\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e 松江\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Heroku\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Web\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Ruby on Rails\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Author\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e DHH\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Products\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Redmine\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e GitLab\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Sinatra\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e DevOps\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Vagrant\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Author\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e M.Hashimoto\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Provider\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Virtual Box\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e VMWare\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Workstation\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Fusion\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e AWS\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Chef\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e Puppet\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e 特徴\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e 楽しい\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e すべてがオブジェクト\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e 可読性の高さ\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e 動的言語\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e 強力なメタプログラミング\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e DSL作成の容易さ\u003c/span\u003e\n\u003cspan class=\"no\"\u003eEOS\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003esp2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"no\"\u003eGottani\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eSpace2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enew\u003c/span\u003e\n\u003cspan class=\"n\"\u003eindent_text\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esp2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003espace2_to_common\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003eindexed_indent_text\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eindent_text\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereverse\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewith_index\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emerge\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003emodule\u003c/span\u003e \u003cspan class=\"nn\"\u003eFamilyable\u003c/span\u003e\n \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ePerson\u003c/span\u003e\n \u003cspan class=\"kp\"\u003eattr_accessor\u003c/span\u003e \u003cspan class=\"ss\"\u003e:value\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003e:level\u003c/span\u003e\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003cspan class=\"n\"\u003epersons\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eindexed_indent_text\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereduce\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003eparent\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eindexed_indent_text\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:level\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:level\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"n\"\u003eparent_ids\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eparent\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enil?\u003c/span\u003e \u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eparent\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:id\u003c/span\u003e\u003cspan class=\"o\"\u003e]]\u003c/span\u003e\n \u003cspan class=\"n\"\u003eperson\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"no\"\u003eFamilyable\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003ePerson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enew\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003eparent_ids\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eparent_ids\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etap\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:value\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:level\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n \u003cspan class=\"n\"\u003epersons\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\n \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003esorted_persons\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereverse\u003c/span\u003e\n\u003cspan class=\"n\"\u003efamily\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"no\"\u003eFamilyable\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eFamily\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enew\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003efamily\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"no\"\u003eGraph\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e\n \u003cspan class=\"n\"\u003eglobal\u003c/span\u003e \u003cspan class=\"ss\"\u003elayout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;twopi\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003eoverlap\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"kp\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003esplines\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"ss\"\u003e:curved\u003c/span\u003e\n \u003cspan class=\"n\"\u003enodes\u003c/span\u003e \u003cspan class=\"ss\"\u003efontname\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;MS GOTHIC\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003eshape\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"ss\"\u003e:none\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003estyle\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;filled\u0026#39;\u003c/span\u003e\n \u003cspan class=\"n\"\u003eedges\u003c/span\u003e \u003cspan class=\"ss\"\u003edir\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"ss\"\u003e:none\u003c/span\u003e\n \u003cspan class=\"c1\"\u003e# ノードの定義\u003c/span\u003e\n \u003cspan class=\"n\"\u003esorted_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eeach\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003enode\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e\u0026quot;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"ss\"\u003elabel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n \u003cspan class=\"c1\"\u003e# エッジの定義\u003c/span\u003e\n \u003cspan class=\"n\"\u003esorted_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eeach\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003eparents\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003efamily\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_parents\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n \u003cspan class=\"k\"\u003enext\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eparents\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eempty?\u003c/span\u003e\n \u003cspan class=\"n\"\u003eparent\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eparents\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efirst\u003c/span\u003e\n \u003cspan class=\"n\"\u003eoptions\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n \u003cspan class=\"n\"\u003eoptions\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:penwidth\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e6\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eparent\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n \u003cspan class=\"n\"\u003eedge\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003eparent\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e_person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e\u0026quot;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eoptions\u003c/span\u003e\n\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n \u003cspan class=\"c1\"\u003e# *** 根のランク, スタイル定義 ***\u003c/span\u003e\n \u003cspan class=\"n\"\u003emin_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\n \u003cspan class=\"n\"\u003emin_person_key\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003emin_id\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e\u0026quot;\u003c/span\u003e\n \u003cspan class=\"n\"\u003erank\u003c/span\u003e \u003cspan class=\"ss\"\u003e:min\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003emin_person_key\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"n\"\u003enode\u003c/span\u003e \u003cspan class=\"n\"\u003emin_person_key\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"ss\"\u003eshape\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;pentagon\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003efontsize\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e80\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003efillcolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"ss\"\u003e:red\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n \u003cspan class=\"c1\"\u003e# *** 根以外のノードのランク定義 ***\u003c/span\u003e\n \u003cspan class=\"n\"\u003eranked_persons\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esorted_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egroup_by\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eselect\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enonzero?\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"n\"\u003eranked_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eeach\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003erank\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eeach_rank_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003eeach_rank_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003enode\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e\u0026quot;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"ss\"\u003efontsize\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e40\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003erank\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n \u003cspan class=\"n\"\u003epersons\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eeach_rank_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026quot;\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eto_sym\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"n\"\u003erank\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003e:same\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n \u003cspan class=\"n\"\u003esave\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;givz_mindmap\u0026quot;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003e:png\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\u003ch2\u003e\n \u003cspan id=\"2-4\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-4\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e出力\n \u003c/h2\u003e\u003cp\u003e\u003cimg src=\"https://qiita-image-store.s3.amazonaws.com/0/43480/b67aaead-ba36-cd32-3211-e89128d87404.png\" title=\"\" alt=\"givz_mindmap.png\"\u003e\u003c/img\u003e\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-5\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-5\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e参照\n \u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eGviz gem\n\u003ca href=\"https://github.com/melborne/Gviz\" title=\"https://github.com/melborne/Gviz\" target=\"_blank\"\u003ehttps://github.com/melborne/Gviz\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003etbpgr_utils gem\n\u003ca href=\"https://github.com/tbpgr/tbpgr_utils\" title=\"https://github.com/tbpgr/tbpgr_utils\" target=\"_blank\"\u003ehttps://github.com/tbpgr/tbpgr_utils\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003egottani gem\n\u003ca href=\"https://github.com/tbpgr/gottani\" title=\"https://github.com/tbpgr/gottani\" target=\"_blank\"\u003ehttps://github.com/tbpgr/gottani\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-6\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-6\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e関連記事\n \u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"http://qiita.com/ryosy383/items/c1644031db7da38323bc\" title=\"\"\u003eryosy383様 Gvizでマインドマップを作成してみた\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n","stock_users":["kasumani","kumazo@github","makyos","umanoda","tamano","ryosy383"]},{"id":132746,"uuid":"770d56834103efe2b723","user":{"id":43480,"url_name":"tbpgr","profile_image_url":"https://pbs.twimg.com/profile_images/378800000650620553/44cda07bda72f68147025c752716b580_normal.png"},"title":"Ruby | Gvizでインデント構造のテキストをグラフに変換する","created_at":"2014-09-12 22:16:47 +0900","updated_at":"2014-09-15 20:19:28 +0900","created_at_in_words":"4日","updated_at_in_words":"約17時間","tags":[{"name":"Ruby","url_name":"ruby","icon_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/9de6a11d330f5694820082438f88ccf4a1b289b2/medium.jpg?1364837630","versions":[]},{"name":"Graphviz","url_name":"graphviz","icon_url":"/icons/medium/missing.png","versions":[]}],"stock_count":4,"comment_count":0,"url":"http://qiita.com/tbpgr/items/770d56834103efe2b723","created_at_as_seconds":1410527807,"tweet":false,"gist_url":null,"private":false,"raw_body":"# Ruby | Gvizでインデント構造のテキストをグラフに変換する\n\n## 概要\nGvizでインデント構造のテキストをグラフに変換します。\n\n## 利用ライブラリ\n* Gviz gem\n* TbpgrUtils gem (自作 gem)\n * 私の遊び場的ユーティリティ。実用性はない。\n * 今回は familyable を利用する。親子関係のデータの取得用。\n* Gottani gem (自作 gem)\n * タブ、スペース、 Markdown の見出し、はてな記法 などツリー構造のテキストの相互変換用ツール\n * 今回は変換時の中間処理用に用意されている、 Array[Hash{:level, :value}] を返却するメソッドを利用\n * level は深さ , value は文字列\n\n## サンプルコード\n\n~~~ruby\nrequire \'familyable\'\nrequire \'gottani_core\'\nrequire \"gviz\"\n\ntext =\u003c\u003c-EOS\nroot\n child1\n child1_1\n child1_1_1\n child1_1_2\n child1_1_2_1\n child1_1_2_2\n child1_2\n child2\n child2_1\n child3\nEOS\n\nsp2 = Gottani::Space2.new\nindent_text = sp2.space2_to_common(text)\n\nindexed_indent_text = indent_text.reverse.map.with_index { |e, i|e.merge({id: i}) }\n# indexed_indent_text はこんなデータが入っています\n# [{:level=\u003e1, :value=\u003e\"child3\", :id=\u003e0},\n# {:level=\u003e2, :value=\u003e\"child2_1\", :id=\u003e1},\n# {:level=\u003e1, :value=\u003e\"child2\", :id=\u003e2},\n# {:level=\u003e2, :value=\u003e\"child1_2\", :id=\u003e3},\n# {:level=\u003e4, :value=\u003e\"child1_1_2_2\", :id=\u003e4},\n# {:level=\u003e4, :value=\u003e\"child1_1_2_1\", :id=\u003e5},\n# {:level=\u003e3, :value=\u003e\"child1_1_2\", :id=\u003e6},\n# {:level=\u003e3, :value=\u003e\"child1_1_1\", :id=\u003e7},\n# {:level=\u003e2, :value=\u003e\"child1_1\", :id=\u003e8},\n# {:level=\u003e1, :value=\u003e\"child1\", :id=\u003e9},\n# {:level=\u003e0, :value=\u003e\"root\", :id=\u003e10}]\n\nmodule Familyable\n class Person\n attr_accessor :value, :level\n end\nend\n\ni = 0\npersons = indexed_indent_text.reduce([]) do |persons, person|\n parent = indexed_indent_text[(i + 1)..-1]\n .find { |e|e[:level] == person[:level] - 1 }\n parent_ids = parent.nil? ? [] : [parent[:id]]\n person = Familyable::Person.new(id: i, parent_ids: parent_ids).tap do |e|\n e.value = person[:value]\n e.level = person[:level]\n end\n persons \u003c\u003c person\n i += 1\n persons\nend\n\nsorted_persons = persons.reverse\nfamily = Familyable::Family.new(family: persons)\n# family にはこんなデータが入っています。\n#\u003cFamilyable::Family:0x00000600291778\n# @family=\n# [#\u003cFamilyable::Person:0x00000600293d70\n# @id=0,\n# @level=1,\n# @parent_ids=[10],\n# @value=\"child3\"\u003e,\n# #\u003cFamilyable::Person:0x00000600293aa0\n# @id=1,\n# @level=2,\n# @parent_ids=[2],\n# @value=\"child2_1\"\u003e,\n# #\u003cFamilyable::Person:0x000006002937f8\n# @id=2,\n# @level=1,\n# @parent_ids=[10],\n# @value=\"child2\"\u003e,\n# #\u003cFamilyable::Person:0x00000600293618\n# @id=3,\n# @level=2,\n# @parent_ids=[9],\n# @value=\"child1_2\"\u003e,\n# #\u003cFamilyable::Person:0x00000600293208\n# @id=4,\n# @level=4,\n# @parent_ids=[6],\n# @value=\"child1_1_2_2\"\u003e,\n# #\u003cFamilyable::Person:0x00000600292d08\n# @id=5,\n# @level=4,\n# @parent_ids=[6],\n# @value=\"child1_1_2_1\"\u003e,\n# #\u003cFamilyable::Person:0x00000600292948\n# @id=6,\n# @level=3,\n# @parent_ids=[8],\n# @value=\"child1_1_2\"\u003e,\n# #\u003cFamilyable::Person:0x000006002925b0\n# @id=7,\n# @level=3,\n# @parent_ids=[8],\n# @value=\"child1_1_1\"\u003e,\n# #\u003cFamilyable::Person:0x00000600292240\n# @id=8,\n# @level=2,\n# @parent_ids=[9],\n# @value=\"child1_1\"\u003e,\n# #\u003cFamilyable::Person:0x00000600291d90\n# @id=9,\n# @level=1,\n# @parent_ids=[10],\n# @value=\"child1\"\u003e,\n# #\u003cFamilyable::Person:0x00000600291a48\n# @id=10,\n# @level=0,\n# @parent_ids=[],\n# @value=\"root\"\u003e]\u003e\n\nGraph do\n global layout:\'dot\'\n # ノードの定義\n sorted_persons.each { |person|node :\"person#{person.id}\", { label: person.value } }\n\n # エッジの定義\n sorted_persons.each do |person|\n parents = family.get_parents(person)\n next if parents.empty?\n parent = parents.first\n edge :\"person#{parent.id}_person#{person.id}\"\n end\n\n # *** 根のランク, スタイル定義 ***\n min_id = persons.find { |e|e.level == 0 }.id\n min_person_key = :\"person#{min_id}\"\n rank :min, [min_person_key]\n node min_person_key, { width: 2, shape: \'doublecircle\' }\n\n # *** 根以外のノードのランク定義 ***\n ranked_persons = sorted_persons.group_by { |e|e.level }.select { |key, value|key.nonzero? }\n ranked_persons.each do |rank, each_rank_persons|\n persons = each_rank_persons.map { |e|\"person#{e.id}\".to_sym }\n rank(:same, persons)\n end\n\n save :\"givz_tree\", :png\nend\n~~~\n\n## 出力\n\n![givz_tree.png](https://qiita-image-store.s3.amazonaws.com/0/43480/1d1fc4f1-65b3-fedb-f6d9-2f16be0fde26.png)\n\n## 補足1\ngottani は space2 インデント以外に\n\n* space4\n* hard tab\n* markdown 見出し\n* hatena 見出し\n\nなどに対応しているため、サンプルコードの text の内容を\n上記のフォーマットに変更し、 `Gottani::Space2.new` の代わりに\n`Gottani::Tagb.new` などを利用すれば別のフォーマットにも対応可能です。\n\n## 補足2\nもう1段階抽象化すれば、\n\n* ツリー構造のテキスト\n* フォーマット(タブ?、インデント?)\n* 表示スタイル(色、形など)\n\nあたりを設定値として与えるだけで、ツリー構造を画像化する\nツールが作成できそうな・・・。\n\n設定の自由度を考えると、画像の出力まですべて行ってしまうよりも、\nGvizのソースを生成して、そのあと表示スタイル部分を好きに変更してもらう\n「消極的な自動生成」※書籍 達人プログラマ参照\nにしたほうが汎用性がありそう。\n\n内部DSLの設定ファイル\n=> ツリー to Gviz を実行\n=> 生成された Gviz の色・形など設定部を変更\n=> Gviz 実行\n=> 画像が生成される。\n\nオプションとして、デフォルトのレイアウトでよければ\n画像の出力まで一気に行うサブコマンドがあってもよさそう。\n\n内部DSLには\n\n* ツリーフォーマットのテキスト\n* 画像ファイル名\n* Gviz のコードを出力する rb ファイル名\n* 表示スタイル(大まかにスタイル指定。細かくやりたい人は自動生成後に手動で)\n\nを設定できるようにすればいい。\n\n## 参照\n* Gviz gem \n https://github.com/melborne/Gviz\n* tbpgr_utils gem \n https://github.com/tbpgr/tbpgr_utils\n* gottani gem \n https://github.com/tbpgr/gottani\n","body":"\u003ch1\u003e\n \u003cspan id=\"1-1\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#1-1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eRuby | Gvizでインデント構造のテキストをグラフに変換する\n \u003c/h1\u003e\u003ch2\u003e\n \u003cspan id=\"2-1\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-1\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e概要\n \u003c/h2\u003e\u003cp\u003eGvizでインデント構造のテキストをグラフに変換します。\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-2\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-2\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e利用ライブラリ\n \u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eGviz gem\u003c/li\u003e\n\u003cli\u003eTbpgrUtils gem (自作 gem)\n\n\u003cul\u003e\n\u003cli\u003e私の遊び場的ユーティリティ。実用性はない。\u003c/li\u003e\n\u003cli\u003e今回は familyable を利用する。親子関係のデータの取得用。\u003c/li\u003e\n\u003c/ul\u003e\u003c/li\u003e\n\u003cli\u003eGottani gem (自作 gem)\n\n\u003cul\u003e\n\u003cli\u003eタブ、スペース、 Markdown の見出し、はてな記法 などツリー構造のテキストの相互変換用ツール\u003c/li\u003e\n\u003cli\u003e今回は変換時の中間処理用に用意されている、 Array[Hash{:level, :value}] を返却するメソッドを利用\u003c/li\u003e\n\u003cli\u003elevel は深さ , value は文字列\u003c/li\u003e\n\u003c/ul\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-3\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-3\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003eサンプルコード\n \u003c/h2\u003e\u003cdiv class=\"code-frame\" data-lang=\"ruby\"\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre\u003e\u003cspan class=\"nb\"\u003erequire\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;familyable\u0026#39;\u003c/span\u003e\n\u003cspan class=\"nb\"\u003erequire\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;gottani_core\u0026#39;\u003c/span\u003e\n\u003cspan class=\"nb\"\u003erequire\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026quot;gviz\u0026quot;\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003etext\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u0026lt;\u0026lt;-\u003c/span\u003e\u003cspan class=\"no\"\u003eEOS\u003c/span\u003e\n\u003cspan class=\"sh\"\u003eroot\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child1\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child1_1\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child1_1_1\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child1_1_2\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child1_1_2_1\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child1_1_2_2\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child1_2\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child2\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child2_1\u003c/span\u003e\n\u003cspan class=\"sh\"\u003e child3\u003c/span\u003e\n\u003cspan class=\"no\"\u003eEOS\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003esp2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"no\"\u003eGottani\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eSpace2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enew\u003c/span\u003e\n\u003cspan class=\"n\"\u003eindent_text\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esp2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003espace2_to_common\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etext\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003eindexed_indent_text\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eindent_text\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereverse\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewith_index\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emerge\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# indexed_indent_text はこんなデータが入っています\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# [{:level=\u0026gt;1, :value=\u0026gt;\u0026quot;child3\u0026quot;, :id=\u0026gt;0},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;2, :value=\u0026gt;\u0026quot;child2_1\u0026quot;, :id=\u0026gt;1},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;1, :value=\u0026gt;\u0026quot;child2\u0026quot;, :id=\u0026gt;2},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;2, :value=\u0026gt;\u0026quot;child1_2\u0026quot;, :id=\u0026gt;3},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;4, :value=\u0026gt;\u0026quot;child1_1_2_2\u0026quot;, :id=\u0026gt;4},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;4, :value=\u0026gt;\u0026quot;child1_1_2_1\u0026quot;, :id=\u0026gt;5},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;3, :value=\u0026gt;\u0026quot;child1_1_2\u0026quot;, :id=\u0026gt;6},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;3, :value=\u0026gt;\u0026quot;child1_1_1\u0026quot;, :id=\u0026gt;7},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;2, :value=\u0026gt;\u0026quot;child1_1\u0026quot;, :id=\u0026gt;8},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;1, :value=\u0026gt;\u0026quot;child1\u0026quot;, :id=\u0026gt;9},\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# {:level=\u0026gt;0, :value=\u0026gt;\u0026quot;root\u0026quot;, :id=\u0026gt;10}]\u003c/span\u003e\n\n\u003cspan class=\"k\"\u003emodule\u003c/span\u003e \u003cspan class=\"nn\"\u003eFamilyable\u003c/span\u003e\n \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ePerson\u003c/span\u003e\n \u003cspan class=\"kp\"\u003eattr_accessor\u003c/span\u003e \u003cspan class=\"ss\"\u003e:value\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003e:level\u003c/span\u003e\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003cspan class=\"n\"\u003epersons\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eindexed_indent_text\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereduce\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003eparent\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eindexed_indent_text\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e.\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:level\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:level\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"n\"\u003eparent_ids\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eparent\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enil?\u003c/span\u003e \u003cspan class=\"p\"\u003e?\u003c/span\u003e \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eparent\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:id\u003c/span\u003e\u003cspan class=\"o\"\u003e]]\u003c/span\u003e\n \u003cspan class=\"n\"\u003eperson\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"no\"\u003eFamilyable\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003ePerson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enew\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003eparent_ids\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eparent_ids\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etap\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:value\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"ss\"\u003e:level\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n \u003cspan class=\"n\"\u003epersons\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\n \u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"o\"\u003e+=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n\u003cspan class=\"n\"\u003esorted_persons\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ereverse\u003c/span\u003e\n\u003cspan class=\"n\"\u003efamily\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"no\"\u003eFamilyable\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"no\"\u003eFamily\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enew\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003efamily\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# family にはこんなデータが入っています。\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e#\u0026lt;Familyable::Family:0x00000600291778\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @family=\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# [#\u0026lt;Familyable::Person:0x00000600293d70\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=0,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=1,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[10],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child3\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x00000600293aa0\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=1,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=2,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[2],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child2_1\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x000006002937f8\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=2,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=1,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[10],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child2\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x00000600293618\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=3,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=2,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[9],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child1_2\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x00000600293208\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=4,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=4,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[6],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child1_1_2_2\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x00000600292d08\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=5,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=4,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[6],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child1_1_2_1\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x00000600292948\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=6,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=3,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[8],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child1_1_2\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x000006002925b0\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=7,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=3,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[8],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child1_1_1\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x00000600292240\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=8,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=2,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[9],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child1_1\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x00000600291d90\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=9,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=1,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[10],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;child1\u0026quot;\u0026gt;,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# #\u0026lt;Familyable::Person:0x00000600291a48\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @id=10,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @level=0,\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @parent_ids=[],\u003c/span\u003e\n\u003cspan class=\"c1\"\u003e# @value=\u0026quot;root\u0026quot;\u0026gt;]\u0026gt;\u003c/span\u003e\n\n\u003cspan class=\"no\"\u003eGraph\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e\n \u003cspan class=\"n\"\u003eglobal\u003c/span\u003e \u003cspan class=\"ss\"\u003elayout\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;dot\u0026#39;\u003c/span\u003e\n \u003cspan class=\"c1\"\u003e# ノードの定義\u003c/span\u003e\n \u003cspan class=\"n\"\u003esorted_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eeach\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003enode\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e\u0026quot;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"ss\"\u003elabel\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n \u003cspan class=\"c1\"\u003e# エッジの定義\u003c/span\u003e\n \u003cspan class=\"n\"\u003esorted_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eeach\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003eparents\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003efamily\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_parents\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n \u003cspan class=\"k\"\u003enext\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eparents\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eempty?\u003c/span\u003e\n \u003cspan class=\"n\"\u003eparent\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eparents\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efirst\u003c/span\u003e\n \u003cspan class=\"n\"\u003eedge\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003eparent\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e_person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e\u0026quot;\u003c/span\u003e\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n \u003cspan class=\"c1\"\u003e# *** 根のランク, スタイル定義 ***\u003c/span\u003e\n \u003cspan class=\"n\"\u003emin_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\n \u003cspan class=\"n\"\u003emin_person_key\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003emin_id\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"ss\"\u003e\u0026quot;\u003c/span\u003e\n \u003cspan class=\"n\"\u003erank\u003c/span\u003e \u003cspan class=\"ss\"\u003e:min\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003emin_person_key\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n \u003cspan class=\"n\"\u003enode\u003c/span\u003e \u003cspan class=\"n\"\u003emin_person_key\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"ss\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003eshape\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;doublecircle\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\n \u003cspan class=\"c1\"\u003e# *** 根以外のノードのランク定義 ***\u003c/span\u003e\n \u003cspan class=\"n\"\u003eranked_persons\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esorted_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egroup_by\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eselect\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enonzero?\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"n\"\u003eranked_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eeach\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003erank\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eeach_rank_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\n \u003cspan class=\"n\"\u003epersons\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eeach_rank_persons\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emap\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026quot;person\u003c/span\u003e\u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026quot;\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eto_sym\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n \u003cspan class=\"n\"\u003erank\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ss\"\u003e:same\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epersons\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\n \u003cspan class=\"n\"\u003esave\u003c/span\u003e \u003cspan class=\"ss\"\u003e:\u0026quot;givz_tree\u0026quot;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ss\"\u003e:png\u003c/span\u003e\n\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/pre\u003e\u003c/div\u003e\u003c/div\u003e\u003ch2\u003e\n \u003cspan id=\"2-4\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-4\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e出力\n \u003c/h2\u003e\u003cp\u003e\u003cimg src=\"https://qiita-image-store.s3.amazonaws.com/0/43480/1d1fc4f1-65b3-fedb-f6d9-2f16be0fde26.png\" title=\"\" alt=\"givz_tree.png\"\u003e\u003c/img\u003e\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-5\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-5\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e補足1\n \u003c/h2\u003e\u003cp\u003egottani は space2 インデント以外に\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003espace4\u003c/li\u003e\n\u003cli\u003ehard tab\u003c/li\u003e\n\u003cli\u003emarkdown 見出し\u003c/li\u003e\n\u003cli\u003ehatena 見出し\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eなどに対応しているため、サンプルコードの text の内容を\u003cbr\u003e\n上記のフォーマットに変更し、 \u003ccode\u003eGottani::Space2.new\u003c/code\u003e の代わりに\u003cbr\u003e\n\u003ccode\u003eGottani::Tagb.new\u003c/code\u003e などを利用すれば別のフォーマットにも対応可能です。\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-6\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-6\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e補足2\n \u003c/h2\u003e\u003cp\u003eもう1段階抽象化すれば、\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003eツリー構造のテキスト\u003c/li\u003e\n\u003cli\u003eフォーマット(タブ?、インデント?)\u003c/li\u003e\n\u003cli\u003e表示スタイル(色、形など)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eあたりを設定値として与えるだけで、ツリー構造を画像化する\u003cbr\u003e\nツールが作成できそうな・・・。\u003c/p\u003e\n\u003cp\u003e設定の自由度を考えると、画像の出力まですべて行ってしまうよりも、\u003cbr\u003e\nGvizのソースを生成して、そのあと表示スタイル部分を好きに変更してもらう\u003cbr\u003e\n「消極的な自動生成」※書籍 達人プログラマ参照\u003cbr\u003e\nにしたほうが汎用性がありそう。\u003c/p\u003e\n\u003cp\u003e内部DSLの設定ファイル\u003cbr\u003e\n=> ツリー to Gviz を実行\u003cbr\u003e\n=> 生成された Gviz の色・形など設定部を変更\u003cbr\u003e\n=> Gviz 実行\u003cbr\u003e\n=> 画像が生成される。\u003c/p\u003e\n\u003cp\u003eオプションとして、デフォルトのレイアウトでよければ\u003cbr\u003e\n画像の出力まで一気に行うサブコマンドがあってもよさそう。\u003c/p\u003e\n\u003cp\u003e内部DSLには\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003eツリーフォーマットのテキスト\u003c/li\u003e\n\u003cli\u003e画像ファイル名\u003c/li\u003e\n\u003cli\u003eGviz のコードを出力する rb ファイル名\u003c/li\u003e\n\u003cli\u003e表示スタイル(大まかにスタイル指定。細かくやりたい人は自動生成後に手動で)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eを設定できるようにすればいい。\u003c/p\u003e\n\u003ch2\u003e\n \u003cspan id=\"2-7\" class=\"fragment\"\u003e\u003c/span\u003e\n \u003ca href=\"#2-7\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u003e\u003c/a\u003e参照\n \u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eGviz gem\u003cbr\u003e\n\u003ca href=\"https://github.com/melborne/Gviz\" title=\"https://github.com/melborne/Gviz\" target=\"_blank\"\u003ehttps://github.com/melborne/Gviz\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003etbpgr_utils gem\u003cbr\u003e\n\u003ca href=\"https://github.com/tbpgr/tbpgr_utils\" title=\"https://github.com/tbpgr/tbpgr_utils\" target=\"_blank\"\u003ehttps://github.com/tbpgr/tbpgr_utils\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003egottani gem\u003cbr\u003e\n\u003ca href=\"https://github.com/tbpgr/gottani\" title=\"https://github.com/tbpgr/gottani\" target=\"_blank\"\u003ehttps://github.com/tbpgr/gottani\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n","stock_users":["kasumani","kumazo@github","umanoda","tamano"]}]'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
cases.each do |c|
|
30
|
+
it "|case_no=#{c[:case_no]}|case_title=#{c[:case_title]}" do
|
31
|
+
begin
|
32
|
+
case_before c
|
33
|
+
|
34
|
+
# -- given --
|
35
|
+
qiita_json_loader = QiitaScouter::QiitaItemsJsonLoader.new
|
36
|
+
qiita_json_loader.stub(:open).and_return(DummyForItem.new)
|
37
|
+
|
38
|
+
# -- when --
|
39
|
+
qiita_json_loader.load(c[:user])
|
40
|
+
actual = qiita_json_loader.articles
|
41
|
+
|
42
|
+
# -- then --
|
43
|
+
expect(actual.articles.size).to eq(c[:expected])
|
44
|
+
ensure
|
45
|
+
case_after c
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def case_before(c) # rubocop:disable UnusedMethodArgument
|
50
|
+
# implement each case before
|
51
|
+
end
|
52
|
+
|
53
|
+
def case_after(c) # rubocop:disable UnusedMethodArgument
|
54
|
+
# implement each case after
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
# rubocop:enable LineLength, UnusedMethodArgument
|