qiita_scouter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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