qiita_scouter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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