when_exe 0.3.6 → 0.3.7

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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +171 -0
  3. data/lib/when_exe.rb +78 -47
  4. data/lib/when_exe/basictypes.rb +752 -747
  5. data/lib/when_exe/calendarnote.rb +805 -801
  6. data/lib/when_exe/calendartypes.rb +1583 -1531
  7. data/lib/when_exe/coordinates.rb +16 -15
  8. data/lib/when_exe/core/duration.rb +114 -110
  9. data/lib/when_exe/core/extension.rb +504 -504
  10. data/lib/when_exe/ephemeris.rb +1917 -1913
  11. data/lib/when_exe/ephemeris/moon.rb +333 -333
  12. data/lib/when_exe/ephemeris/notes.rb +389 -387
  13. data/lib/when_exe/ephemeris/planets.rb +585 -585
  14. data/lib/when_exe/ephemeris/sun.rb +214 -214
  15. data/lib/when_exe/googlecalendar.rb +144 -140
  16. data/lib/when_exe/icalendar.rb +1636 -1636
  17. data/lib/when_exe/inspect.rb +46 -22
  18. data/lib/when_exe/locales/akt.rb +176 -176
  19. data/lib/when_exe/locales/encoding_conversion.rb +134 -126
  20. data/lib/when_exe/locales/iast.rb +90 -90
  21. data/lib/when_exe/locales/locale.rb +750 -746
  22. data/lib/when_exe/locales/transliteration_table.rb +62 -62
  23. data/lib/when_exe/mini_application.rb +307 -305
  24. data/lib/when_exe/parts/enumerator.rb +2 -2
  25. data/lib/when_exe/parts/geometric_complex.rb +397 -397
  26. data/lib/when_exe/parts/method_cash.rb +224 -224
  27. data/lib/when_exe/parts/resource.rb +1069 -1071
  28. data/lib/when_exe/parts/timezone.rb +240 -230
  29. data/lib/when_exe/region/armenian.rb +56 -56
  30. data/lib/when_exe/region/babylonian.rb +405 -0
  31. data/lib/when_exe/region/bahai.rb +146 -146
  32. data/lib/when_exe/region/balinese.rb +622 -622
  33. data/lib/when_exe/region/chinese.rb +95 -25
  34. data/lib/when_exe/region/chinese/calendars.rb +1016 -1016
  35. data/lib/when_exe/region/chinese/epochs.rb +1 -1
  36. data/lib/when_exe/region/chinese/twins.rb +803 -795
  37. data/lib/when_exe/region/christian.rb +824 -824
  38. data/lib/when_exe/region/coptic.rb +106 -87
  39. data/lib/when_exe/region/discordian.rb +225 -225
  40. data/lib/when_exe/region/far_east.rb +188 -188
  41. data/lib/when_exe/region/french.rb +56 -56
  42. data/lib/when_exe/region/geologicalage.rb +639 -639
  43. data/lib/when_exe/region/goddess.rb +58 -58
  44. data/lib/when_exe/region/indian.rb +1254 -1251
  45. data/lib/when_exe/region/iranian.rb +8 -8
  46. data/lib/when_exe/region/islamic.rb +3 -3
  47. data/lib/when_exe/region/japanese.rb +93 -99
  48. data/lib/when_exe/region/japanese/calendars.rb +396 -397
  49. data/lib/when_exe/region/japanese/epochs.rb +26 -26
  50. data/lib/when_exe/region/japanese/nihon_shoki.rb +71 -71
  51. data/lib/when_exe/region/japanese/notes.rb +1383 -1386
  52. data/lib/when_exe/region/japanese/residues.rb +1306 -1306
  53. data/lib/when_exe/region/japanese/twins.rb +225 -225
  54. data/lib/when_exe/region/japanese/weeks.rb +112 -0
  55. data/lib/when_exe/region/javanese.rb +230 -230
  56. data/lib/when_exe/region/jewish.rb +126 -126
  57. data/lib/when_exe/region/korean.rb +378 -378
  58. data/lib/when_exe/region/m17n.rb +114 -113
  59. data/lib/when_exe/region/martian.rb +258 -255
  60. data/lib/when_exe/region/mayan.rb +32 -32
  61. data/lib/when_exe/region/residue.rb +89 -89
  62. data/lib/when_exe/region/roman.rb +36 -24
  63. data/lib/when_exe/region/ryukyu.rb +97 -97
  64. data/lib/when_exe/region/shire.rb +240 -240
  65. data/lib/when_exe/region/soviet.rb +209 -0
  66. data/lib/when_exe/region/symmetry.rb +50 -50
  67. data/lib/when_exe/region/thai.rb +336 -335
  68. data/lib/when_exe/region/tibetan.rb +316 -315
  69. data/lib/when_exe/region/vietnamese.rb +440 -439
  70. data/lib/when_exe/region/weekdate.rb +80 -80
  71. data/lib/when_exe/region/world.rb +175 -175
  72. data/lib/when_exe/region/yerm.rb +14 -14
  73. data/lib/when_exe/region/zoroastrian.rb +203 -203
  74. data/lib/when_exe/timestandard.rb +707 -681
  75. data/lib/when_exe/tmduration.rb +338 -330
  76. data/lib/when_exe/tmobjects.rb +1346 -1325
  77. data/lib/when_exe/tmposition.rb +2115 -2072
  78. data/lib/when_exe/tmreference.rb +1693 -1669
  79. data/lib/when_exe/version.rb +1 -1
  80. data/link_to_online_documents +1 -1
  81. data/test/examples/JapanHolidaysRFC6350.ics +1 -1
  82. data/test/test.rb +67 -61
  83. data/test/test/basictypes.rb +409 -409
  84. data/test/test/calendarnote.rb +86 -69
  85. data/test/test/calendartypes.rb +97 -97
  86. data/test/test/coordinates.rb +396 -396
  87. data/test/test/ephemeris.rb +83 -74
  88. data/test/test/ephemeris/moon.rb +14 -14
  89. data/test/test/ephemeris/planets.rb +14 -14
  90. data/test/test/ephemeris/sun.rb +14 -14
  91. data/test/test/googlecalendar.rb +194 -176
  92. data/test/test/icalendar.rb +867 -858
  93. data/test/test/inspect.rb +117 -117
  94. data/test/test/parts.rb +487 -487
  95. data/test/test/region/balinese.rb +34 -0
  96. data/test/test/region/chinese.rb +218 -206
  97. data/test/test/region/christian.rb +245 -245
  98. data/test/test/region/coptic.rb +27 -27
  99. data/test/test/region/french.rb +33 -33
  100. data/test/test/region/geologicalage.rb +17 -17
  101. data/test/test/region/indian.rb +57 -57
  102. data/test/test/region/iran.rb +54 -54
  103. data/test/test/region/islamic.rb +18 -18
  104. data/test/test/region/japanese.rb +237 -219
  105. data/test/test/region/jewish.rb +61 -61
  106. data/test/test/region/m17n.rb +184 -184
  107. data/test/test/region/mayan.rb +195 -195
  108. data/test/test/region/residue.rb +147 -139
  109. data/test/test/region/thai.rb +116 -116
  110. data/test/test/region/tibetan.rb +30 -30
  111. data/test/test/region/vietnamese.rb +102 -102
  112. data/test/test/region/yerm.rb +146 -146
  113. data/test/test/timestandard.rb +81 -81
  114. data/test/test/tmobjects.rb +328 -328
  115. data/test/test/tmposition.rb +397 -284
  116. data/test/test/tmreference.rb +157 -157
  117. metadata +13 -10
@@ -1,224 +1,224 @@
1
- # -*- coding: utf-8 -*-
2
- =begin
3
- Copyright (C) 2011-2014 Takashi SUGA
4
-
5
- You may use and/or modify this file according to the license described in the LICENSE.txt file included in this archive.
6
- =end
7
-
8
- module When
9
- #
10
- # 本ライブラリのための諸々の部品
11
- #
12
- module Parts
13
-
14
- #
15
- # == メソッドの実行結果をキャッシュし処理の高速化を行う
16
- #
17
- # === fn というメソッドをキャッシュ化
18
- # * fn ではなく fn_ を定義しておく
19
- # * fn メソッドが呼ばれると fn_ を実行し、{引数=>戻り値} を Hash に記憶する
20
- # * 同じ引数で再度 fn メソッドが呼ばれると Hash から戻り値を取り出して返す
21
- #
22
- # === a_to_b と b_to_a という互いに逆関数のメソッドをキャッシュ化
23
- # * a_to_b ではなく a_to_b_ , b_to_a ではなく b_to_a_ を定義しておく
24
- # * a_to_b メソッドが呼ばれると a_to_b_ を実行し、{引数=>戻り値}, {戻り値=>引数}を Hash に記憶する
25
- # * 同じ引数で再度 a_to_b メソッドが呼ばれると Hash から戻り値を取り出して返す
26
- # * b_to_a メソッドが呼ばれ Hash に戻り値があれば Hash から戻り値を取り出して返す
27
- #
28
- # == 特記事項
29
- #
30
- # === Argument identification
31
- # The eql? method of When::TM::(Temporal)Position is not overridden.
32
- # It seems that the cost of identification of the argument exceeds the merit of the method cash.
33
- # I do not recommend applying the methodcash to the method which takes
34
- # When::TM::(Temporal)Position as an argument.
35
- #
36
- # === Multi-thread critical situation
37
- # There is a problem in consistency of hash when this function is used in multi-thread environment.
38
- # If the initialize method sets Mutex in instance variable @_m_cash_lock_,
39
- # this function gives up use of hash in the critical situation.
40
- #
41
- # class Foo
42
- # include MethodCash
43
- #
44
- # def initialize
45
- # ...
46
- # @_m_cash_lock_ = Mutex.new
47
- # ...
48
- # end
49
- # end
50
- #
51
- # 参考 http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/47663
52
- #
53
- module MethodCash
54
-
55
- # @private
56
- Escape = {:to_str => true,
57
- :to_ary => true,
58
- :to_hash => true}
59
-
60
- class << self
61
-
62
- # '_' で終わるメソッドをキャッシュせずに毎回計算するか否か
63
- #
64
- # @return [Boolean] キャッシュしない true, キャッシュする false/nil
65
- #
66
- attr_accessor :direct
67
-
68
- # When::Parts::MethodCash のグローバルな設定を行う
69
- #
70
- # @param [Hash] options 以下の通り
71
- # @option options [Boolean] :direct '_' で終わるメソッドをキャッシュせずに毎回計算するか否か
72
- # @option options [Hash{Symbol=>boolean}] :escape 毎回 method_missing を発生させるメソッドを true にする
73
- # @option options [false, nil] :escape to_str, to_ary, to_hash のみ毎回 method_missing を発生させる
74
- # @option options [true] :escape すべて毎回 method_missing を発生させる
75
- #
76
- # @return [void]
77
- #
78
- # @note When::TM::Calendar クラスの一部はキャッシュ使用を前提としているため :direct=>true では動作しません
79
- #
80
- def _setup_(options={})
81
- @_setup_info = options
82
- @direct = options[:direct]
83
- case options[:escape]
84
- when true
85
- instance_eval %Q{
86
- def escape(method)
87
- true
88
- end
89
- }
90
- when Hash
91
- @escape = Escape.merge(options[:escape])
92
- instance_eval %Q{
93
- def escape(method)
94
- @escape[method]
95
- end
96
- }
97
- else
98
- instance_eval %Q{
99
- def escape(method)
100
- Escape.key?(method)
101
- end
102
- }
103
- end
104
- end
105
-
106
- # 設定情報を取得する
107
- #
108
- # @return [Hash] 設定情報
109
- #
110
- def _setup_info
111
- @_setup_info ||= {}
112
- end
113
-
114
- # method_missing メソッドを forward するか否か
115
- #
116
- # @param [Symbol] method メソッドシンボル
117
- #
118
- # @return [boolean] true - しない, false/nil - する
119
- #
120
- def escape(method)
121
- Escape.key?(method)
122
- end
123
- end
124
-
125
- alias :method_missing_ :method_missing
126
-
127
- #
128
- # 最初に発生する method_missing で、キャッシュ機能を登録する
129
- #
130
- # @param [Symbol] name メソッド名
131
- # @param [Array] args メソッド引数
132
- # @param [block] block ブロック(省略可)
133
- #
134
- # @return [void]
135
- #
136
- def method_missing(name, *args, &block)
137
-
138
- return method_missing_(name, *args, &block) unless respond_to?("#{name}_", true)
139
- return send("#{name}_", *args, &block) if MethodCash.direct
140
-
141
- if ((name.to_s =~ /^(_*)(.+?)_to_(.+)$/) && respond_to?("#{$1}#{$3}_to_#{$2}_", true))
142
- prefix, from, to = $~[1..3]
143
- begin
144
- if (@_m_cash_lock_)
145
- return send("#{prefix}#{from}_to_#{to}_", *args, &block) unless @_m_cash_lock_.try_lock
146
- unlock = "ensure ; @_m_cash_lock_.locked? && @_m_cash_lock_.unlock"
147
- end
148
- [[from, to],[to, from]].each do |pair|
149
- a, b = pair
150
- lock = @_m_cash_lock_ ? " return #{prefix}#{a}_to_#{b}_(*args) unless @_m_cash_lock_.try_lock" : ''
151
- instance_eval %Q{
152
- def #{prefix}#{a}_to_#{b}(*args)
153
- key = _key_simplefy(args)
154
- inv = @_m_cash_["#{prefix}#{a}_to_#{b}"][key]
155
- return inv if inv
156
- begin
157
- #{lock}
158
- inv = #{prefix}#{a}_to_#{b}_(*args)
159
- @_m_cash_["#{prefix}#{b}_to_#{a}"][_key_simplefy(inv)] = args
160
- @_m_cash_["#{prefix}#{a}_to_#{b}"][key] = inv
161
- return inv
162
- #{unlock}
163
- end
164
- end
165
- }
166
- end
167
- key = _key_simplefy(args)
168
- inv = send("#{prefix}#{from}_to_#{to}_", *args)
169
- @_m_cash_ ||= {}
170
- @_m_cash_["#{prefix}#{to}_to_#{from}"] ||= {}
171
- @_m_cash_["#{prefix}#{from}_to_#{to}"] ||= {}
172
- @_m_cash_["#{prefix}#{to}_to_#{from}"][_key_simplefy(inv)] = args
173
- @_m_cash_["#{prefix}#{from}_to_#{to}"][key] = inv
174
- return inv
175
- ensure
176
- @_m_cash_lock_ && @_m_cash_lock_.locked? && @_m_cash_lock_.unlock
177
- end
178
-
179
- else
180
- begin
181
- respond = respond_to?("#{name}_setup", true)
182
- setup = respond ? "#{name}_setup(key, *args)" :
183
- "(@_m_cash_[\"#{name}\"][key] = #{name}_(*args))"
184
- if (@_m_cash_lock_)
185
- return send("#{name}_", *args, &block) unless @_m_cash_lock_.try_lock
186
- lock = " return #{name}_(*args) unless @_m_cash_lock_.try_lock"
187
- unlock = "ensure ; @_m_cash_lock_.locked? && @_m_cash_lock_.unlock"
188
- end
189
- instance_eval %Q{
190
- def #{name}(*args)
191
- key = _key_simplefy(args)
192
- ret = @_m_cash_["#{name}"][key]
193
- return ret if ret
194
- begin
195
- #{lock}
196
- return #{setup}
197
- #{unlock}
198
- end
199
- end
200
- }
201
- key = _key_simplefy(args)
202
- @_m_cash_ ||= {}
203
- @_m_cash_["#{name}"] ||= {}
204
- if (respond)
205
- return send("#{name}_setup", key, *args)
206
- else
207
- return(@_m_cash_["#{name}"][key] ||= send("#{name}_", *args))
208
- end
209
- ensure
210
- @_m_cash_lock_ && @_m_cash_lock_.locked? && @_m_cash_lock_.unlock
211
- end
212
- end
213
- end
214
-
215
- private
216
-
217
- def _key_simplefy(args)
218
- key = args.kind_of?(Array) ? args.dup : args
219
- key = key[0] while key.kind_of?(Array) && key.length<=1
220
- return key
221
- end
222
- end
223
- end
224
- end
1
+ # -*- coding: utf-8 -*-
2
+ =begin
3
+ Copyright (C) 2011-2014 Takashi SUGA
4
+
5
+ You may use and/or modify this file according to the license described in the LICENSE.txt file included in this archive.
6
+ =end
7
+
8
+ module When
9
+ #
10
+ # 本ライブラリのための諸々の部品
11
+ #
12
+ module Parts
13
+
14
+ #
15
+ # == メソッドの実行結果をキャッシュし処理の高速化を行う
16
+ #
17
+ # === fn というメソッドをキャッシュ化
18
+ # * fn ではなく fn_ を定義しておく
19
+ # * fn メソッドが呼ばれると fn_ を実行し、{引数=>戻り値} を Hash に記憶する
20
+ # * 同じ引数で再度 fn メソッドが呼ばれると Hash から戻り値を取り出して返す
21
+ #
22
+ # === a_to_b と b_to_a という互いに逆関数のメソッドをキャッシュ化
23
+ # * a_to_b ではなく a_to_b_ , b_to_a ではなく b_to_a_ を定義しておく
24
+ # * a_to_b メソッドが呼ばれると a_to_b_ を実行し、{引数=>戻り値}, {戻り値=>引数}を Hash に記憶する
25
+ # * 同じ引数で再度 a_to_b メソッドが呼ばれると Hash から戻り値を取り出して返す
26
+ # * b_to_a メソッドが呼ばれ Hash に戻り値があれば Hash から戻り値を取り出して返す
27
+ #
28
+ # == 特記事項
29
+ #
30
+ # === Argument identification
31
+ # The eql? method of When::TM::(Temporal)Position is not overridden.
32
+ # It seems that the cost of identification of the argument exceeds the merit of the method cash.
33
+ # I do not recommend applying the methodcash to the method which takes
34
+ # When::TM::(Temporal)Position as an argument.
35
+ #
36
+ # === Multi-thread critical situation
37
+ # There is a problem in consistency of hash when this function is used in multi-thread environment.
38
+ # If the initialize method sets Mutex in instance variable @_m_cash_lock_,
39
+ # this function gives up use of hash in the critical situation.
40
+ #
41
+ # class Foo
42
+ # include MethodCash
43
+ #
44
+ # def initialize
45
+ # ...
46
+ # @_m_cash_lock_ = Mutex.new
47
+ # ...
48
+ # end
49
+ # end
50
+ #
51
+ # 参考 http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/47663
52
+ #
53
+ module MethodCash
54
+
55
+ # @private
56
+ Escape = {:to_str => true,
57
+ :to_ary => true,
58
+ :to_hash => true}
59
+
60
+ class << self
61
+
62
+ # '_' で終わるメソッドをキャッシュせずに毎回計算するか否か
63
+ #
64
+ # @return [Boolean] キャッシュしない true, キャッシュする false/nil
65
+ #
66
+ attr_accessor :direct
67
+
68
+ # When::Parts::MethodCash のグローバルな設定を行う
69
+ #
70
+ # @param [Hash] options 以下の通り
71
+ # @option options [Boolean] :direct '_' で終わるメソッドをキャッシュせずに毎回計算するか否か
72
+ # @option options [Hash{Symbol=>boolean}] :escape 毎回 method_missing を発生させるメソッドを true にする
73
+ # @option options [false, nil] :escape to_str, to_ary, to_hash のみ毎回 method_missing を発生させる
74
+ # @option options [true] :escape すべて毎回 method_missing を発生させる
75
+ #
76
+ # @return [void]
77
+ #
78
+ # @note When::TM::Calendar クラスの一部はキャッシュ使用を前提としているため :direct=>true では動作しません
79
+ #
80
+ def _setup_(options={})
81
+ @_setup_info = options
82
+ @direct = options[:direct]
83
+ case options[:escape]
84
+ when true
85
+ instance_eval %Q{
86
+ def escape(method)
87
+ true
88
+ end
89
+ }
90
+ when Hash
91
+ @escape = Escape.merge(options[:escape])
92
+ instance_eval %Q{
93
+ def escape(method)
94
+ @escape[method]
95
+ end
96
+ }
97
+ else
98
+ instance_eval %Q{
99
+ def escape(method)
100
+ Escape.key?(method)
101
+ end
102
+ }
103
+ end
104
+ end
105
+
106
+ # 設定情報を取得する
107
+ #
108
+ # @return [Hash] 設定情報
109
+ #
110
+ def _setup_info
111
+ @_setup_info ||= {}
112
+ end
113
+
114
+ # method_missing メソッドを forward するか否か
115
+ #
116
+ # @param [Symbol] method メソッドシンボル
117
+ #
118
+ # @return [boolean] true - しない, false/nil - する
119
+ #
120
+ def escape(method)
121
+ Escape.key?(method)
122
+ end
123
+ end
124
+
125
+ alias :method_missing_ :method_missing
126
+
127
+ #
128
+ # 最初に発生する method_missing で、キャッシュ機能を登録する
129
+ #
130
+ # @param [Symbol] name メソッド名
131
+ # @param [Array] args メソッド引数
132
+ # @param [block] block ブロック(省略可)
133
+ #
134
+ # @return [void]
135
+ #
136
+ def method_missing(name, *args, &block)
137
+
138
+ return method_missing_(name, *args, &block) unless respond_to?("#{name}_", true)
139
+ return send("#{name}_", *args, &block) if MethodCash.direct
140
+
141
+ if ((name.to_s =~ /\A(_*)(.+?)_to_(.+)\z/) && respond_to?("#{$1}#{$3}_to_#{$2}_", true))
142
+ prefix, from, to = $~[1..3]
143
+ begin
144
+ if (@_m_cash_lock_)
145
+ return send("#{prefix}#{from}_to_#{to}_", *args, &block) unless @_m_cash_lock_.try_lock
146
+ unlock = "ensure ; @_m_cash_lock_.locked? && @_m_cash_lock_.unlock"
147
+ end
148
+ [[from, to],[to, from]].each do |pair|
149
+ a, b = pair
150
+ lock = @_m_cash_lock_ ? " return #{prefix}#{a}_to_#{b}_(*args) unless @_m_cash_lock_.try_lock" : ''
151
+ instance_eval %Q{
152
+ def #{prefix}#{a}_to_#{b}(*args)
153
+ key = _key_simplefy(args)
154
+ inv = @_m_cash_["#{prefix}#{a}_to_#{b}"][key]
155
+ return inv if inv
156
+ begin
157
+ #{lock}
158
+ inv = #{prefix}#{a}_to_#{b}_(*args)
159
+ @_m_cash_["#{prefix}#{b}_to_#{a}"][_key_simplefy(inv)] = args
160
+ @_m_cash_["#{prefix}#{a}_to_#{b}"][key] = inv
161
+ return inv
162
+ #{unlock}
163
+ end
164
+ end
165
+ }
166
+ end
167
+ key = _key_simplefy(args)
168
+ inv = send("#{prefix}#{from}_to_#{to}_", *args)
169
+ @_m_cash_ ||= {}
170
+ @_m_cash_["#{prefix}#{to}_to_#{from}"] ||= {}
171
+ @_m_cash_["#{prefix}#{from}_to_#{to}"] ||= {}
172
+ @_m_cash_["#{prefix}#{to}_to_#{from}"][_key_simplefy(inv)] = args
173
+ @_m_cash_["#{prefix}#{from}_to_#{to}"][key] = inv
174
+ return inv
175
+ ensure
176
+ @_m_cash_lock_ && @_m_cash_lock_.locked? && @_m_cash_lock_.unlock
177
+ end
178
+
179
+ else
180
+ begin
181
+ respond = respond_to?("#{name}_setup", true)
182
+ setup = respond ? "#{name}_setup(key, *args)" :
183
+ "(@_m_cash_[\"#{name}\"][key] = #{name}_(*args))"
184
+ if (@_m_cash_lock_)
185
+ return send("#{name}_", *args, &block) unless @_m_cash_lock_.try_lock
186
+ lock = " return #{name}_(*args) unless @_m_cash_lock_.try_lock"
187
+ unlock = "ensure ; @_m_cash_lock_.locked? && @_m_cash_lock_.unlock"
188
+ end
189
+ instance_eval %Q{
190
+ def #{name}(*args)
191
+ key = _key_simplefy(args)
192
+ ret = @_m_cash_["#{name}"][key]
193
+ return ret if ret
194
+ begin
195
+ #{lock}
196
+ return #{setup}
197
+ #{unlock}
198
+ end
199
+ end
200
+ }
201
+ key = _key_simplefy(args)
202
+ @_m_cash_ ||= {}
203
+ @_m_cash_["#{name}"] ||= {}
204
+ if (respond)
205
+ return send("#{name}_setup", key, *args)
206
+ else
207
+ return(@_m_cash_["#{name}"][key] ||= send("#{name}_", *args))
208
+ end
209
+ ensure
210
+ @_m_cash_lock_ && @_m_cash_lock_.locked? && @_m_cash_lock_.unlock
211
+ end
212
+ end
213
+ end
214
+
215
+ private
216
+
217
+ def _key_simplefy(args)
218
+ key = args.kind_of?(Array) ? args.dup : args
219
+ key = key[0] while key.kind_of?(Array) && key.length<=1
220
+ return key
221
+ end
222
+ end
223
+ end
224
+ end
@@ -1,1071 +1,1069 @@
1
- # -*- coding: utf-8 -*-
2
- =begin
3
- Copyright (C) 2011-2014 Takashi SUGA
4
-
5
- You may use and/or modify this file according to the license described in the LICENSE.txt file included in this archive.
6
- =end
7
-
8
- #
9
- # 本ライブラリのための諸々の部品
10
- #
11
- module When::Parts
12
-
13
- #
14
- # Resource which has 'International Resource Identifier'
15
- #
16
- module Resource
17
-
18
- # @private
19
- LabelProperty = nil
20
-
21
- # @private
22
- ConstTypes = {
23
- 'Terms' => ['BasicTypes::M17n', '%s', '_m:%s%s' ],
24
- 'Era' => ['TM::CalendarEra', '%s', '_e:%s%s' ],
25
- 'Residue' => ['Coordinates', '%s', '_co:%s%s' ],
26
- 'Week' => ['CalendarNote', '%sWeek', '_n:%sWeek%s' ],
27
- 'Note' => ['CalendarNote', '%s', '_n:%s%s' ],
28
- 'Notes' => ['CalendarNote', '%s', '_n:%s/Notes%s'],
29
- nil => ['CalendarTypes', '%s', '_c:%s%s' ]
30
- }
31
-
32
- # @private
33
- ConstList = []
34
-
35
- # private
36
- IRIHeader = /^[_a-z\d]+:[^:]/i
37
-
38
- # @private
39
- class ContentLine
40
-
41
- RFC6350 = {
42
- "\\\\" => "\\",
43
- "\\n" => "\n",
44
- "\\N" => "\n",
45
- "\\;" => ";",
46
- "\\:" => ":"
47
- }
48
-
49
- RFC6868 = {
50
- "^n" => "\n",
51
- "^^" => "^",
52
- "^'" => '"'
53
- }
54
-
55
- attr_reader :predicate
56
- attr_accessor :object
57
- attr_reader :attribute
58
- attr_accessor :namespace
59
- attr_accessor :same_altid
60
- attr_reader :marked
61
-
62
- def initialize(key, object=nil, marked=nil)
63
- key = key.downcase.gsub(/-/,'_') if (key==key.upcase)
64
- @predicate, @namespace = key.split(/:/).reverse
65
- object = object.gsub(/\^./) {|escape| RFC6868[escape] || escape} if object.instance_of?(String)
66
- @object = object
67
- @marked = marked
68
- @attribute = {}
69
- end
70
- end
71
-
72
- # @private
73
- module Synchronize
74
-
75
- # 排他実行
76
- #
77
- # 与えられたブロックを必要なら排他制御をして実行する
78
- #
79
- def synchronize
80
- if @_lock_
81
- @_lock_.synchronize do
82
- yield
83
- end
84
- else
85
- yield
86
- end
87
- end
88
- end
89
-
90
- #
91
- # Resource の has-a 親子関係を管理する
92
- #
93
- module Pool
94
-
95
- include Synchronize
96
-
97
- # 初期化
98
- # @return [void]
99
- #
100
- # @note
101
- # 本メソッドでマルチスレッド対応の管理変数の初期化を行っている。
102
- # このため、本メソッド自体はスレッドセーフでない。
103
- #
104
- def _setup_
105
- @_lock_ = Mutex.new if When.multi_thread
106
- @_pool = {}
107
- end
108
-
109
- # オブジェクト参照
110
- #
111
- # @param [String] label
112
- #
113
- # @return [When::Parts::Resource] 指定した label で登録した子 Resource を返す
114
- #
115
- def [](label)
116
-
117
- # nil label の場合
118
- return _pool[label] unless label
119
-
120
- # 階層がある場合
121
- terms = Resource._encode(label).split(/::/)
122
- terms.shift if terms[0] == ''
123
- return terms.inject(self) {|obj,term| obj = obj[Resource._decode(term)]} if terms.length >= 2
124
-
125
- # 階層がない場合
126
- _pool[Resource._decode(Resource._extract_prefix(terms[0]))]
127
- end
128
-
129
- # オブジェクト登録
130
- #
131
- # 指定した label で子 Resource を登録する
132
- #
133
- # @param [String] label
134
- # @param [When::Parts::Resource] obj
135
- #
136
- # @return [void]
137
- #
138
- def []=(label, obj)
139
- # raise NameError, "Name duplication" if (@_pool[label])
140
- _pool[label] = obj
141
- end
142
-
143
- # @private
144
- def pool_keys
145
- _pool.keys
146
- end
147
-
148
- # @private
149
- def _pool
150
- _setup_ unless @_pool
151
- @_pool
152
- end
153
- end
154
-
155
- class << self
156
-
157
- include Pool
158
-
159
- # Base URI for When_exe Resources
160
- #
161
- # @return [String]
162
- #
163
- def base_uri
164
- @base_uri ||= When::SourceURI
165
- end
166
-
167
- # Root Directory for When_exe Resources
168
- #
169
- # @return [String]
170
- #
171
- def root_dir
172
- @root_dir ||= When::RootDir
173
- end
174
-
175
- #
176
- # 略称を iri に変換する
177
- #
178
- # @param [String or Symbol] abbreviation 略称
179
- #
180
- # @return [String] iri or nil
181
- #
182
- # @private
183
- def _abbreviation_to_iri(abbreviation, abbreviation_types=ConstTypes)
184
- abbreviation_types[:pattern] ||= /^([A-Z].*?)(#{abbreviation_types.keys.compact.join('|')})?(\?.+|::.+)?$/
185
- abbreviation =~ abbreviation_types[:pattern]
186
- return nil unless $1
187
- klass, name, iri = abbreviation_types[$2]
188
- if klass.kind_of?(String)
189
- klass = klass.split('::').inject(When) {|k,n| k.const_get(n)}
190
- abbreviation_types[$2][0] = klass
191
- end
192
- klass.const_defined?(name % $1) ? iri % [$1,$3] : nil
193
- end
194
-
195
- # @private
196
- attr_reader :_prefix, :_prefix_values, :_prefix_index
197
- private :_prefix, :_prefix_values, :_prefix_index
198
-
199
- # 初期化
200
- #
201
- # @param [Hash] options 以下の通り
202
- # @option options [String] :base_uri Base URI for When_exe Resources (Default When::SourceURI)
203
- # @option options [String] :root_dir Root Directory for When_exe Resources Cash data (Default When::RootDir)
204
- # @option options [Boolean] :leave_const If true, leave Constants of When module defined
205
- #
206
- # @return [void]
207
- #
208
- # @note
209
- # 本メソッドでマルチスレッド対応の管理変数の初期化を行っている。
210
- # このため、本メソッド自体はスレッドセーフでない。
211
- #
212
- def _setup_(options={})
213
- super()
214
- @_prefix = {
215
- '_wp' => 'http://en.wikipedia.org/wiki/',
216
- '_w' => base_uri + '/',
217
- '_p' => base_uri + 'Parts/',
218
- '_b' => base_uri + 'BasicTypes/',
219
- '_m' => base_uri + 'BasicTypes/M17n/',
220
- '_co' => base_uri + 'Coordinates/',
221
- '_l' => base_uri + 'Coordinates/Spatial?',
222
- '_v' => base_uri + 'V/',
223
- '_rs' => base_uri + 'RS/',
224
- '_ex' => base_uri + 'EX/',
225
- '_tm' => base_uri + 'TM/',
226
- '_e' => base_uri + 'TM/CalendarEra/',
227
- '_t' => base_uri + 'TimeStandard/',
228
- '_ep' => base_uri + 'Ephemeris/',
229
- '_c' => base_uri + 'CalendarTypes/',
230
- '_n' => base_uri + 'CalendarNote/',
231
- '_sc' => base_uri + 'Ephemeris/V50/'
232
- }
233
- @base_uri = options[:base_uri] || When::SourceURI
234
- @root_dir = options[:root_dir] || When::RootDir
235
- @_prefix_values = @_prefix.values.sort.reverse
236
- @_prefix_index = @_prefix.invert
237
- unless options[:leave_const] || ConstList.empty?
238
- ConstList.delete_if do |constant|
239
- When.send(:remove_const, constant) if When.const_defined?(constant)
240
- true
241
- end
242
- When._define_common_calendar_types
243
- end
244
- end
245
-
246
- # 設定情報を取得する
247
- #
248
- # @return [Hash] 設定情報
249
- #
250
- def _setup_info
251
- {:base_uri => base_uri, :root_dir => root_dir}
252
- end
253
-
254
- # オブジェクト生成&参照
255
- #
256
- # 指定した iri When::Parts::Resource オブジェクトを取得する。
257
- # 当該オブジェクトが未登録であれば生成する。
258
- #
259
- # @param [String] iri International Resource Identifier
260
- # @param [String] namespace (デフォルトの名前空間, 指定がないときは名前空間を省略しない)
261
- #
262
- # @return [When::Parts::Resource]
263
- #
264
- def _instance(iri, namespace=nil)
265
- _setup_ unless @_pool
266
-
267
- case iri
268
- when Array ; return iri.map {|e| _instance(e, namespace)} # 配列は個別に処理
269
- when Resource ; return iri # 登録済みはそのまま
270
- when String ; # 解析処理へ
271
- else ; raise ArgumentError, "can't convert #{iri.class} to String" # 例外
272
- end
273
-
274
- # 内部文字列化
275
- iri = When::EncodingConversion.to_internal_encoding(iri)
276
-
277
- # 階層がある場合は、階層をたどる
278
- iri = Resource._decode(iri)
279
- iri = $1 while iri =~ /^\((.*)\)$/
280
- iri = namespace + iri if namespace && iri !~ IRIHeader
281
- root, *leaves= Resource._encode(iri).split(/::/)
282
- if leaves.size > 0
283
- return leaves.inject(_instance(Resource._decode(root))) {|obj,leaf| obj[Resource._decode(leaf)]}
284
- end
285
-
286
- # 登録ずみなら、参照
287
- iri = _extract_prefix(iri)
288
- path, query = iri.split(/\?/, 2)
289
- if When.multi_thread
290
- my_mutex = nil
291
- @_lock_.synchronize do
292
- @_pool ||= {}
293
- unless @_pool[iri]
294
- my_mutex = Mutex.new
295
- @_pool[iri] = my_mutex
296
- end
297
- end
298
- case @_pool[iri]
299
- when my_mutex; my_mutex.synchronize {@_pool[iri] = _create_object(iri, path, query) }
300
- when Mutex ; @_pool[iri].synchronize {@_pool[iri]}
301
- else ; @_pool[iri]
302
- end
303
- else
304
- @_pool ||= {}
305
- @_pool[iri] ||= _create_object(iri, path, query)
306
- end
307
- end
308
-
309
- # @private
310
- def _path_with_prefix(obj, simple=true)
311
- _setup_ unless @_pool
312
- path = obj.kind_of?(Class) ? obj.to_s.sub(/^When::/, base_uri).gsub(/::/, '/') :
313
- obj.iri
314
- simple ? _simplify_path(path) : path
315
- end
316
-
317
- # @private
318
- def _simplify_path(path)
319
- _prefix_values.each do |value|
320
- index = path.index(value)
321
- return _prefix_index[value] + ':' + path[value.length..-1] if index
322
- end
323
- return path
324
- end
325
-
326
- # @private
327
- def _parse(line, type=nil)
328
- return line unless line.kind_of?(String)
329
- line.sub!(/\s#.*$/, '')
330
- return When::Locale._split($1) if type && /^#{type}:(.+)$/i =~ line
331
- tokens = line.scan(/((?:[^\\:]|\\.)+)(?::(?!\z))?|:/).flatten
332
- return When::Locale._split(line) unless tokens.size > 1 && /^(\*)?([A-Z][-A-Z_]{0,255})(?:;(.+))?$/i =~ tokens[0]
333
- marked, key, property = $~[1..3]
334
- values = tokens[1..-1]
335
- value = values.join(':') unless values == [nil]
336
- content = ContentLine.new(key, value, marked)
337
- value ||= ''
338
- if property
339
- content.attribute['.'] = property + ':' + value
340
- property.scan(/((?:[^\\;]|\\.)+)(?:;(?!\z))?|;/).flatten.each do |pr|
341
- pr ||= ''
342
- pr.gsub!(/\\./) {|escape| ContentLine::RFC6350[escape] || escape}
343
- prop = ContentLine.new(*pr.split(/=/, 2))
344
- content.attribute[prop.predicate] = prop
345
- end
346
- else
347
- content.attribute['.'] = value
348
- end
349
- return content
350
- end
351
-
352
- # @private
353
- def _extract_prefix(path, capitalize=false)
354
- if (path =~ /^(.+?):+(.+)$/)
355
- prefix, klass = $~[1..2]
356
- if capitalize
357
- prefix = '_' + prefix.downcase
358
- klass = klass.capitalize if klass == klass.upcase
359
- end
360
- path = _prefix[prefix] + klass if (_prefix[prefix])
361
- elsif capitalize && path =~ /^(v[^\/]+|daylight$|standard$)/i
362
- klass = path.sub(/^v/i, '').capitalize
363
- path = _prefix['_v'] + klass if When::V.const_defined?(klass) &&
364
- When::V.const_get(klass).kind_of?(Class)
365
- end
366
- return path
367
- end
368
-
369
- # @private
370
- def _replace_tags(source, tags)
371
- case source
372
- when When::BasicTypes::M17n
373
- source
374
- when String
375
- target = source.dup
376
- tags.each_pair do |key, value|
377
- target.gsub!(/#\{([?&][^=#}]+?=)?#{key}(:.*?)?\}/, '\1' + value) if value.kind_of?(String)
378
- end
379
- target.gsub(/#\{.+?(:(.*?))?\}/, '\2')
380
- when Array
381
- source.map {|target| _replace_tags(target, tags)}
382
- when Hash
383
- target = {}
384
- source.each_pair do |key, value|
385
- target[key] = _replace_tags(tags[key] || value, tags)
386
- end
387
- target
388
- else
389
- source
390
- end
391
- end
392
-
393
- # @private
394
- def _encode(iri)
395
- return iri unless iri =~ /\(/
396
-
397
- iri = iri.dup
398
- begin
399
- unless iri.gsub!(/\([^()]*\)/) {|token|
400
- token.gsub(/[():?&%]/) {|char|'%' + char.ord.to_s(16)}
401
- }
402
- raise ArgumentError, 'Brackets do not correspond: ' + iri
403
- end
404
- end while iri =~ /\(/
405
- iri
406
- end
407
-
408
- # @private
409
- def _decode(iri)
410
- return iri unless iri =~ /%28/
411
-
412
- iri = iri.dup
413
- begin
414
- unless iri.gsub!(/%28.*?%29/) {|token|
415
- token.gsub(/%([\dA-F]{2})/i) {$1.to_i(16).chr}
416
- }
417
- raise ArgumentError, 'Brackets do not correspond: ' + iri
418
- end
419
- end while iri =~ /%28/
420
- iri = $1 if iri =~ /^\((.*)\)$/
421
- iri
422
- end
423
-
424
- # @private
425
- def _instantiate(resource)
426
- return resource unless resource.kind_of?(Array)
427
- return resource[0].new(*resource[1..-1]) if resource[0].kind_of?(Class)
428
- return resource.map {|rsc| _instantiate(rsc)}
429
- end
430
-
431
- private
432
-
433
- # オブジェクト生成
434
- def _create_object(iri, path, query)
435
- # query analyzation
436
- options = {}
437
- replace = {}
438
- if query
439
- options = Hash[*Resource._encode(query).split(/&/).map{|pair|
440
- key, value = pair.split(/=/, 2)
441
- [key, Resource._decode(value)]
442
- }.flatten]
443
- keys = options.keys
444
- keys.each do |key|
445
- replace[$1] = options.delete(key) if key =~ /^([A-Z].*)/
446
- end
447
- end
448
- options['..'] = iri
449
-
450
- # internal Resource
451
- if path.index(Resource.base_uri) == 0
452
- list = _class(path)
453
- if list
454
- return _internal(list, replace, options)
455
- else
456
- raise IOError, 'IRI not found - ' + path
457
- end
458
- end
459
-
460
- # external Resource
461
- begin
462
- object = When::Locale.send(:wikipedia_object, path, {:query=>query})
463
- return object if object
464
- OpenURI
465
- args = [path, "1".respond_to?(:force_encoding) ? 'r:utf-8' : 'r']
466
- args << {:ssl_verify_mode=>OpenSSL::SSL::VERIFY_NONE} if path =~ /^https:/
467
- open(*args) do |file|
468
- resource = file.read
469
- case resource[0..5].upcase
470
- when 'BEGIN:'
471
- options['.'] = _ics(_replace_tags(resource, replace).split(/[\n\r]+/))
472
- options['.'][0].new(options)
473
- when '<?XML '
474
- options['.'] = _xml(REXML::Document.new(_replace_tags(resource, replace)).root)
475
- options['.'][0].new(options)
476
- else
477
- raise NoMethodError, 'JSON not supported' unless Object.const_defined?(:JSON)
478
- _internal(_json([JSON.parse(resource)]), replace, options)
479
- end
480
- end
481
- rescue OpenURI::HTTPError => error
482
- message = error.message + " - #{path}"
483
- error = error.respond_to?(:uri) ?
484
- error.class.new(message, error.io, error.uri) :
485
- error.class.new(message, error.io)
486
- raise error
487
- end
488
- end
489
-
490
- # 内部形式定義の取得
491
- def _class(path)
492
- list = [When]
493
- path[Resource.base_uri.length..-1].split(/\//).each do |mod|
494
- if list[0].kind_of?(Module) && list[0].const_defined?(mod)
495
- list.unshift(list[0].const_get(mod))
496
- else
497
- return nil unless list[0] == When::V
498
- list.unshift(When::V::Root)
499
- return list
500
- end
501
- end
502
- return list
503
- end
504
-
505
- # 内部形式定義のオブジェクト化
506
- def _internal(list, replace, options)
507
- case list[0]
508
- when Class
509
- list[0].new(options)
510
- when Array
511
- top = list[0][0]
512
- if top.kind_of?(Hash)
513
- top.each_pair do |key, value|
514
- replace.update(value[replace[key]]) if value.kind_of?(Hash) && value[replace[key]]
515
- end
516
- list[0] = list[0][1..-1]
517
- list[0] = _replace_tags(list[0], top.merge(replace))
518
- end
519
- if list[0][0].kind_of?(Class)
520
- # 配列の先頭がクラスである場合
521
- klass, *list = list[0]
522
- unless list[-1].kind_of?(Hash)
523
- if list.length == 1
524
- list[0] = {'.'=>Array(list[0])}
525
- else
526
- list << {}
527
- end
528
- end
529
- else
530
- # 配列の先頭がクラスではない場合
531
- klass, *list = [list[1], *list[0]]
532
- list << {} unless list[-1].kind_of?(Hash)
533
- end
534
- list[-1] = list[-1].merge(options)
535
- klass.new(*list)
536
- else
537
- list[0]
538
- end
539
- end
540
-
541
- # .xml フォーマットの読み込み
542
- def _xml(xml, namespace={})
543
- obj = [_class(_extract_prefix(xml.attributes['type'].to_s))[0]]
544
- xml.attributes.each_pair do |key,value|
545
- expanded_name = value.expanded_name
546
- next unless (expanded_name =~ /^xmlns/)
547
- key = '' if expanded_name == 'xmlns'
548
- namespace[key] = value.to_s
549
- end
550
- obj << ContentLine.new('xmlns:namespace', namespace) if (namespace.size>0)
551
- xml.each do |e|
552
- next unless defined? e.name
553
- if (e.attributes['type'])
554
- obj << _xml(e, namespace)
555
- else
556
- content = ContentLine.new(e.expanded_name, e.attributes['ref']||e.text)
557
- e.attributes.each_pair do |key,value|
558
- attr = ContentLine.new(value.name, value)
559
- attr.namespace = value.prefix
560
- content.attribute[key] = attr
561
- end
562
- obj << content
563
- end
564
- end
565
- return obj
566
- end
567
-
568
- # .ics フォーマットの読み込み
569
- def _ics(ics, type=nil)
570
- obj = [type] if type
571
- indent = nil
572
- while (line = ics.shift) do
573
- line.chomp!
574
- case line
575
- when /^\s*BEGIN:(.*)$/
576
- if (type)
577
- obj[-1] = _parse(obj[-1], type) if obj.length > 1
578
- obj << _ics(ics, $1)
579
- else
580
- type = $1
581
- obj = [type]
582
- end
583
- when /^\s*END:(.*)$/
584
- raise TypeError, "Irregal Type : #{$1}" unless (type == $1)
585
- obj[0] = _class(_extract_prefix(type, true))[0]
586
- obj[-1] = _parse(obj[-1], type)
587
- return obj
588
- when /^\s*#/
589
- when /^(\s*)(.*)$/
590
- indent = $1 unless indent
591
- if (indent.length < $1.length)
592
- obj[-1] += line[(indent.length+1)..-1] # See RFC5545 3.1 Content Lines
593
- else
594
- obj << $2
595
- obj[-2] = _parse(obj[-2], type)
596
- end
597
- end
598
- end
599
- raise ArgumentError, "BEGIN-END mismatch"
600
- end
601
-
602
- # .json フォーマットの読み込み
603
- def _json(json)
604
- case json
605
- when Array
606
- json.map {|value| _json(value)}
607
- when Hash
608
- hash = {}
609
- json.each_pair {|key, value| hash[key] = _json(value)}
610
- hash
611
- when String
612
- return json unless json =~ /^When::/
613
- begin
614
- return json.split('::').inject(Object) {|ns, sym| ns.const_get(sym)}
615
- rescue
616
- json
617
- end
618
- else
619
- json
620
- end
621
- end
622
- end
623
-
624
- include Synchronize
625
-
626
- # @private
627
- attr_reader :_pool
628
-
629
- # self has-a 関係で包含するオブジェクト
630
- #
631
- # @return [Array<When::Parts::Resource>]
632
- #
633
- attr_accessor :child
634
- private :child=
635
-
636
- #
637
- # Resource包含階層で使用する namespace
638
- #
639
- # When::BasicTypes::M17n の生成に使用する namespace を定義する。
640
- # RFC 5545 に対する拡張である。
641
- # xml で記述する場合には、本ライブラリ以外でも namespace を定義している。
642
- #
643
- # @return [Hash] { prefix => prefix文字列 }
644
- #
645
- attr_reader :namespace
646
-
647
- #
648
- # Resource包含階層で使用する locale
649
- #
650
- # When::BasicTypes::M17n の生成に使用する locale を定義する。
651
- # RFC 5545 に対する拡張である。
652
- #
653
- # @return [Array<String>]
654
- #
655
- attr_reader :locale
656
-
657
- # strftime で有効な locale
658
- #
659
- # @return [Array<String>]
660
- #
661
- attr_reader :keys
662
-
663
- # オブジェクトの IRI
664
- #
665
- # @param [Boolean] prefix true ならIRI の先頭部分を簡約表現にする
666
- #
667
- # @return [Sring]
668
- #
669
- def iri(prefix=false)
670
- unless @iri
671
- root = @_pool['..']
672
- path = root.instance_of?(String) ? root : label.to_s
673
- if root.respond_to?(:iri)
674
- root_iri = root.iri
675
- path = root_iri + '::' + path if root_iri
676
- end
677
- @iri = path
678
- end
679
- prefix ? Resource._simplify_path(@iri) : @iri
680
- end
681
-
682
- # IRI または child の番号によるオブジェクト参照
683
- #
684
- # @param [String] iri オブジェクトの IRI
685
- # @param [Numeric] iri child index
686
- #
687
- # @return [When::parts::Resource]
688
- #
689
- def [](iri)
690
- case iri
691
- when Numeric
692
- return child[iri * 1]
693
- when String
694
- obj = self
695
- Resource._encode(iri).split(/::/).each do |label|
696
- return obj.child if label == '*'
697
- if obj == Resource
698
- obj = Resource._instance(Resource._decode(label))
699
- else
700
- case label
701
- when '' ; obj = Resource
702
- when '.' # obj = obj
703
- else ; obj = obj._pool[Resource._decode(label)]
704
- end
705
- end
706
- raise ArgumentError, "IRI not found: #{iri}" unless obj
707
- end
708
- return obj
709
- else
710
- super(iri)
711
- #raise ArgumentError, "IRI not found: #{iri}"
712
- end
713
- end
714
-
715
- # self を直接に包含するオブジェクト
716
- #
717
- # @return [When::Parts::Resource]
718
- #
719
- def parent
720
- @_pool['..'].kind_of?(Resource) ? @_pool['..'] : nil
721
- end
722
-
723
- # self を包含するオブジェクト階層
724
- #
725
- # @param [Class] klass 階層を遡るクラス
726
- #
727
- # @return [When::Parts::Resource]
728
- #
729
- def hierarchy(klass=self.class)
730
- hierarchy = []
731
- parent = self
732
- while parent.kind_of?(klass)
733
- hierarchy << parent
734
- parent = parent.parent
735
- end
736
- hierarchy.reverse
737
- end
738
-
739
- # self が other を包含するか
740
- #
741
- # @return [Boolean]
742
- # [ true - 包含する ]
743
- # [ false - 包含しない ]
744
- #
745
- def include?(other)
746
- c = other
747
- while c.kind_of?(Resource)
748
- return true if c.equal?(self)
749
- c = c.parent
750
- end
751
- return false
752
- end
753
-
754
- # other が self を包含するか
755
- #
756
- # @return [Boolean]
757
- # [ true - 包含される ]
758
- # [ false - 包含されない ]
759
- #
760
- def included?(other)
761
- other.include?(self)
762
- end
763
-
764
- # 前のオブジェクト
765
- #
766
- # @return [When::Parts::Resource]
767
- #
768
- def prev
769
- c = self
770
- c = c.child[0] while c.child && c.child.size > 0
771
- c = c._pool['.<-']
772
- c = c.child[-1] while c && c.child && c.child.size > 0
773
- c
774
- end
775
-
776
- # 次のオブジェクト
777
- #
778
- # @return [When::Parts::Resource]
779
- #
780
- def next
781
- c = self
782
- c = c.child[0] while c.child && c.child.size > 0
783
- c._pool['.->']
784
- end
785
- alias :succ :next
786
-
787
- # オブジェクト包含階層の末端か?
788
- #
789
- # @return [Boolean]
790
- # [ true - IRIが付与された他のオブジェクトを包含していない ]
791
- # [ false - IRIが付与された他のオブジェクトを包含している ]
792
- #
793
- def leaf?
794
- !@child || (@child.length==0)
795
- end
796
-
797
- # IRIが付与されているか?
798
- #
799
- # @return [Boolean]
800
- # [ true - IRIが付与されている ]
801
- # [ false - IRIが付与されていない ]
802
- #
803
- def registered?
804
- leaf = self
805
- while leaf._pool['..'].respond_to?(:_pool)
806
- root = leaf._pool['..']
807
- return false unless leaf.equal?(root._pool[leaf.label])
808
- leaf = root
809
- end
810
- Resource._pool.value?(leaf)
811
- end
812
-
813
- # When::BasicTypes::M17n の生成/参照
814
- #
815
- # @param [When::BasicTypes::M17n] source 処理を行わず、そのままsourceを返す
816
- # @param [String] source locale と 文字列の対応
817
- # @param [Array] source 要素を個別に解釈して生成したオブジェクトのArrayを返す
818
- # @param [Hash] namespace prefix の指定
819
- # @param [Array] locale locale の定義順序の指定
820
- # @param [Hash] options (see {When::BasicTypes::M17n.new}[link:When/BasicTypes/M17n.html#method-c-new])
821
- #
822
- # @return [When::BasicTypes::M17n or Array<them>]
823
- #
824
- def m17n(source, namespace=nil, locale=nil, options={})
825
- case source
826
- when Array ; When::BasicTypes::M17n.new(source, namespace, locale, options)
827
- when When::BasicTypes::M17n ; source
828
- when String
829
- return self[$1] if source =~ /^\s*\[((\.{1,2}|::)+[^\]]+)\]/
830
- When::BasicTypes::M17n.new(source, namespace, locale, options)
831
- else ; raise TypeError, "Invalid Type: #{source.class}"
832
- end
833
- end
834
-
835
- # オブジェクトを順に取り出す enumerator
836
- #
837
- # @param [Symbol] direction 取り出す方向
838
- # [ :forward - 昇順 ]
839
- # [ :reverse - 降順 ]
840
- #
841
- def enum_for(direction=:forward)
842
- Enumerator.new(self, direction)
843
- end
844
- alias :to_enum :enum_for
845
-
846
- # Enumerator 生成のダミー
847
- #
848
- # @param [Object] other
849
- #
850
- # @return [Enumerator]
851
- #
852
- def ^(other)
853
- return nil
854
- end
855
-
856
- # 順次実行
857
- #
858
- # @param [Array] args 各サブクラスの enum_for にそのまま渡す
859
- # @param [Block] block 実行するブロック
860
- #
861
- # @return [Enumerator]
862
- #
863
- def each(*args, &block)
864
- enum = enum_for(*args)
865
- return enum unless block
866
- enum.each(&block)
867
- end
868
-
869
- # map, collect の再定義
870
- #
871
- # has-a 関係の子 Resource に対して map/collect を行う
872
- #
873
- # @param [Block] block 実行するブロック
874
- #
875
- # @return [Array]
876
- #
877
- def map(&block)
878
- @child.map(&block)
879
- end
880
- alias :collect :map
881
-
882
- protected
883
-
884
- # @private
885
- def pool_keys
886
- _pool.keys
887
- end
888
-
889
- private
890
-
891
- # 属性の設定
892
- def _attributes(args)
893
- options =_get_options(args)
894
- _set_variables(options)
895
- return args, options
896
- end
897
-
898
- # option の読み出し
899
- def _get_options(args)
900
- options = args[-1].kind_of?(Hash) ? args.pop : {}
901
- @_pool = {}
902
- @_pool['..'] = options['..'] if (options['..'])
903
-
904
- # 配下のオブジェクトの生成と関連付け
905
- if (options.key?('.'))
906
- _child(options)
907
- end
908
-
909
- options
910
- end
911
-
912
- # 変数の設定
913
- def _set_variables(options)
914
- @options = options[:options] || {} if options.key?(:options)
915
- options.each_pair do |key,value|
916
- unless (key =~ /^options$|^\.|^[A-Z]/)
917
- case "#{key}"
918
- when 'namespace' ; value = When::Locale._namespace(value)
919
- when 'locale' ; value = When::Locale._locale(value)
920
- end
921
- instance_variable_set("@#{key}", value)
922
- end
923
- end
924
- end
925
-
926
- # 配下のオブジェクトの生成
927
- def _child(options)
928
- @child = []
929
- query = options.dup
930
- options['..'] = self
931
- leaves = options.delete('.').map {|leaf| Resource._parse(leaf)}
932
- key_list = []
933
- properties = {}
934
- label_candidates = nil
935
-
936
- # ContentLine の処理(namespace, locale, altidの前処理)
937
- leaves.each do |content|
938
- next unless content.kind_of?(ContentLine)
939
- key = content.predicate
940
- value = content.object
941
- next options.delete(key) unless value
942
- case key
943
- when 'namespace'
944
- options[key] ||= {}
945
- if content.attribute['altid']
946
- options[key][content.attribute['prefix'].object] = content.object
947
- else
948
- options[key] = options[key].update(When::Locale._namespace(value))
949
- end
950
- when 'locale'
951
- options[key] = When::Locale._locale(value)
952
- else
953
- _parse_altid(properties, content)
954
- key_list << key unless key_list.include?(key)
955
- end
956
- end
957
-
958
- # ContentLine の処理(一般)
959
- key_list.each do |key|
960
- content = properties[key][0]
961
- value = content.same_altid ? When::BasicTypes::M17n.new(content, options['namespace'], []) : content.object
962
- value = When::BasicTypes::M17n.new(value, nil, nil, options) if value.instance_of?(String) && value =~ /^\s*\[/
963
- @_pool[value.to_s] = value if value.kind_of?(When::BasicTypes::M17n)
964
- if content.marked || key == self.class::LabelProperty
965
- @label = value
966
- else
967
- options[key] = value
968
- label_candidates ||= value
969
- end
970
- end
971
-
972
- # Array の処理(子オブジェクトの生成)
973
- leaves.each do |content|
974
- next unless content.kind_of?(Array)
975
- if content[0].kind_of?(Class)
976
- list = []
977
- content.each do |e|
978
- if e.kind_of?(Hash)
979
- list += e.keys.map {|key| Resource::ContentLine.new(key, e[key])}
980
- else
981
- list << e
982
- end
983
- end
984
- options['.'] = list
985
- @child << content[0].new(options.dup)
986
- else
987
- options.delete('.')
988
- @child << self.class.new(*(content + [options]))
989
- end
990
- end
991
-
992
- # 代表ラベルの設定
993
- options.update(query)
994
- unless @label
995
- raise ArgumentError, "label attribute not found: #{options['.']}" unless label_candidates
996
- @label = label_candidates
997
- end
998
- end
999
-
1000
- # ALTIDを持つ ContentLine の解析
1001
- def _parse_altid(properties, content)
1002
- key = content.predicate
1003
- properties[key] ||= []
1004
- if content.attribute['altid']
1005
- found = false
1006
- (0...properties[key].length).to_a.each do |i|
1007
- prev = properties[key][i]
1008
- if prev.attribute['altid'] && prev.attribute['altid'].object == content.attribute['altid'].object
1009
- content.same_altid = prev
1010
- properties[key][i] = content
1011
- found = true
1012
- break
1013
- end
1014
- end
1015
- end
1016
- properties[key] << content unless found
1017
- end
1018
-
1019
- # 配下のオブジェクトの前後関係の設定
1020
- def _sequence
1021
- return unless @child
1022
- prev = @_pool['..'].child[-1] if @_pool['..'].respond_to?(:child)
1023
- @child.each do |v|
1024
- if prev
1025
- v._pool['.<-'] = prev
1026
- prev._pool['.->'] = v
1027
- while (prev.child && prev.child[-1]) do
1028
- prev = prev.child[-1]
1029
- prev._pool['.->'] = v
1030
- end
1031
- end
1032
- @_pool[v.label.to_s] = v
1033
- prev = v
1034
- end
1035
- end
1036
-
1037
- alias :__method_missing :method_missing
1038
-
1039
- # その他のメソッド
1040
- # When::Parts::Resource で定義されていないメソッドは
1041
- # 処理を @child (type: Array) に委譲する
1042
- #
1043
- def method_missing(name, *args, &block)
1044
- return __method_missing(name, *args, &block) if When::Parts::MethodCash::Escape.key?(name)
1045
- self.class.module_eval %Q{
1046
- def #{name}(*args, &block)
1047
- @child.send("#{name}", *args, &block)
1048
- end
1049
- } unless When::Parts::MethodCash.escape(name)
1050
- @child.send(name, *args, &block)
1051
- end
1052
-
1053
- #
1054
- # オブジェクトを順に取り出す enumerator
1055
- #
1056
- class Enumerator < When::Parts::Enumerator
1057
-
1058
- #
1059
- # 次のオブジェクトを取り出す
1060
- #
1061
- # @return [When::Parts::Resource]
1062
- #
1063
- def succ
1064
- value = @current
1065
- @current = (@current==:first) ? @first : ((@direction == :reverse) ? @current.prev : @current.next)
1066
- @current = nil unless @first.leaf? || @first.include?(@current)
1067
- return value
1068
- end
1069
- end
1070
- end
1071
- end
1
+ # -*- coding: utf-8 -*-
2
+ =begin
3
+ Copyright (C) 2011-2014 Takashi SUGA
4
+
5
+ You may use and/or modify this file according to the license described in the LICENSE.txt file included in this archive.
6
+ =end
7
+
8
+ #
9
+ # 本ライブラリのための諸々の部品
10
+ #
11
+ module When::Parts
12
+
13
+ #
14
+ # Resource which has 'International Resource Identifier'
15
+ #
16
+ module Resource
17
+
18
+ # @private
19
+ LabelProperty = nil
20
+
21
+ # @private
22
+ ConstTypes = {
23
+ 'SolarTerms' => ['CalendarNote', '%sSolarTerms', '_n:%sSolarTerms%s' ],
24
+ 'LunarPhases' => ['LunarPhases', '%sLunarPhases', '_n:%sLunarPhases%s'],
25
+ 'Terms' => ['BasicTypes::M17n', '%s', '_m:%s%s' ],
26
+ 'Era' => ['TM::CalendarEra', '%s', '_e:%s%s' ],
27
+ 'Residue' => ['Coordinates', '%s', '_co:%s%s' ],
28
+ 'Week' => ['CalendarNote', '%sWeek', '_n:%sWeek%s' ],
29
+ 'Note' => ['CalendarNote', '%s', '_n:%s%s' ],
30
+ 'Notes' => ['CalendarNote', '%s', '_n:%s/Notes%s' ],
31
+ nil => ['CalendarTypes', '%s', '_c:%s%s' ]
32
+ }
33
+
34
+ # @private
35
+ ConstList = []
36
+
37
+ # private
38
+ IRIHeader = /\A[_a-z\d]+:[^:]/i
39
+
40
+ # @private
41
+ class ContentLine
42
+
43
+ RFC6350 = {
44
+ "\\\\" => "\\",
45
+ "\\n" => "\n",
46
+ "\\N" => "\n",
47
+ "\\;" => ";",
48
+ "\\:" => ":"
49
+ }
50
+
51
+ RFC6868 = {
52
+ "^n" => "\n",
53
+ "^^" => "^",
54
+ "^'" => '"'
55
+ }
56
+
57
+ attr_reader :predicate
58
+ attr_accessor :object
59
+ attr_reader :attribute
60
+ attr_accessor :namespace
61
+ attr_accessor :same_altid
62
+ attr_reader :marked
63
+
64
+ def initialize(key, object=nil, marked=nil)
65
+ key = key.downcase.gsub(/-/,'_') if (key==key.upcase)
66
+ @predicate, @namespace = key.split(/:/).reverse
67
+ object = object.gsub(/\^./) {|escape| RFC6868[escape] || escape} if object.instance_of?(String)
68
+ @object = object
69
+ @marked = marked
70
+ @attribute = {}
71
+ end
72
+ end
73
+
74
+ # @private
75
+ module Synchronize
76
+
77
+ # 排他実行
78
+ #
79
+ # 与えられたブロックを必要なら排他制御をして実行する
80
+ #
81
+ def synchronize
82
+ if @_lock_
83
+ @_lock_.synchronize do
84
+ yield
85
+ end
86
+ else
87
+ yield
88
+ end
89
+ end
90
+ end
91
+
92
+ #
93
+ # Resource の has-a 親子関係を管理する
94
+ #
95
+ module Pool
96
+
97
+ include Synchronize
98
+
99
+ # 初期化
100
+ # @return [void]
101
+ #
102
+ # @note
103
+ # 本メソッドでマルチスレッド対応の管理変数の初期化を行っている。
104
+ # このため、本メソッド自体はスレッドセーフでない。
105
+ #
106
+ def _setup_
107
+ @_lock_ = Mutex.new if When.multi_thread
108
+ @_pool = {}
109
+ end
110
+
111
+ # オブジェクト参照
112
+ #
113
+ # @param [String] label
114
+ #
115
+ # @return [When::Parts::Resource] 指定した label で登録した子 Resource を返す
116
+ #
117
+ def [](label)
118
+
119
+ # nil label の場合
120
+ return _pool[label] unless label
121
+
122
+ # 階層がある場合
123
+ terms = Resource._encode(label).split(/::/)
124
+ terms.shift if terms[0] == ''
125
+ return terms.inject(self) {|obj,term| obj = obj[Resource._decode(term)]} if terms.length >= 2
126
+
127
+ # 階層がない場合
128
+ _pool[Resource._decode(Resource._extract_prefix(terms[0]))]
129
+ end
130
+
131
+ # オブジェクト登録
132
+ #
133
+ # 指定した label で子 Resource を登録する
134
+ #
135
+ # @param [String] label
136
+ # @param [When::Parts::Resource] obj
137
+ #
138
+ # @return [void]
139
+ #
140
+ def []=(label, obj)
141
+ # raise NameError, "Name duplication" if (@_pool[label])
142
+ _pool[label] = obj
143
+ end
144
+
145
+ # @private
146
+ def pool_keys
147
+ _pool.keys
148
+ end
149
+
150
+ # @private
151
+ def _pool
152
+ _setup_ unless @_pool
153
+ @_pool
154
+ end
155
+ end
156
+
157
+ class << self
158
+
159
+ include Pool
160
+
161
+ # Base URI for When_exe Resources
162
+ #
163
+ # @return [String]
164
+ #
165
+ def base_uri
166
+ @base_uri ||= When::SourceURI
167
+ end
168
+
169
+ # Root Directory for When_exe Resources
170
+ #
171
+ # @return [String]
172
+ #
173
+ def root_dir
174
+ @root_dir ||= When::RootDir
175
+ end
176
+
177
+ #
178
+ # 略称を iri に変換する
179
+ #
180
+ # @param [String or Symbol] abbreviation 略称
181
+ #
182
+ # @return [String] iri or nil
183
+ #
184
+ # @private
185
+ def _abbreviation_to_iri(abbreviation, abbreviation_types=ConstTypes)
186
+ abbreviation_types[:pattern] ||= /\A(?=[A-Z])(.*?)(#{abbreviation_types.keys.compact.join('|')})?(\?.+|::.+)?\z/
187
+ abbreviation.to_s =~ abbreviation_types[:pattern]
188
+ return nil unless $1
189
+ klass, name, iri = abbreviation_types[$2]
190
+ key, name, iri = $2, name % $1, iri % [$1,$3]
191
+ if klass.kind_of?(String)
192
+ klass = klass.split('::').inject(When) {|k,n| k.const_get(n)}
193
+ abbreviation_types[key][0] = klass
194
+ end
195
+ klass.const_defined?(name) ? iri : nil
196
+ end
197
+
198
+ # @private
199
+ attr_reader :_prefix, :_prefix_values, :_prefix_index
200
+ private :_prefix, :_prefix_values, :_prefix_index
201
+
202
+ # 初期化
203
+ #
204
+ # @param [Hash] options 以下の通り
205
+ # @option options [String] :base_uri Base URI for When_exe Resources (Default When::SourceURI)
206
+ # @option options [Hash<String(namespace)=>String(URI)>] :additional_namespaces User defined namespaces (Default {})
207
+ # @option options [String] :root_dir Root Directory for When_exe Resources Cash data (Default When::RootDir)
208
+ # @option options [Boolean] :leave_const If true, leave Constants of When module defined
209
+ #
210
+ # @return [void]
211
+ #
212
+ # @note
213
+ # 本メソッドでマルチスレッド対応の管理変数の初期化を行っている。
214
+ # このため、本メソッド自体はスレッドセーフでない。
215
+ #
216
+ def _setup_(options={})
217
+ super()
218
+ @_prefix = {
219
+ '_wp' => 'http://en.wikipedia.org/wiki/',
220
+ '_w' => base_uri + '/',
221
+ '_p' => base_uri + 'Parts/',
222
+ '_b' => base_uri + 'BasicTypes/',
223
+ '_m' => base_uri + 'BasicTypes/M17n/',
224
+ '_co' => base_uri + 'Coordinates/',
225
+ '_l' => base_uri + 'Coordinates/Spatial?',
226
+ '_v' => base_uri + 'V/',
227
+ '_rs' => base_uri + 'RS/',
228
+ '_ex' => base_uri + 'EX/',
229
+ '_tm' => base_uri + 'TM/',
230
+ '_e' => base_uri + 'TM/CalendarEra/',
231
+ '_t' => base_uri + 'TimeStandard/',
232
+ '_ep' => base_uri + 'Ephemeris/',
233
+ '_c' => base_uri + 'CalendarTypes/',
234
+ '_n' => base_uri + 'CalendarNote/',
235
+ '_sc' => base_uri + 'Ephemeris/V50/'
236
+ }
237
+ @base_uri = options[:base_uri] || When::SourceURI
238
+ @root_dir = options[:root_dir] || When::RootDir
239
+ @_prefix = options[:additional_namespaces].merge(@_prefix) if options[:additional_namespaces].kind_of?(Hash)
240
+ @_prefix_values = @_prefix.values.sort.reverse
241
+ @_prefix_index = @_prefix.invert
242
+ unless options[:leave_const] || ConstList.empty?
243
+ ConstList.delete_if do |constant|
244
+ When.send(:remove_const, constant) if When.const_defined?(constant)
245
+ true
246
+ end
247
+ When._define_common_calendar_types
248
+ end
249
+ end
250
+
251
+ # 設定情報を取得する
252
+ #
253
+ # @return [Hash] 設定情報
254
+ #
255
+ def _setup_info
256
+ {:base_uri => base_uri, :root_dir => root_dir}
257
+ end
258
+
259
+ # オブジェクト生成&参照
260
+ #
261
+ # 指定した iri の When::Parts::Resource オブジェクトを取得する。
262
+ # 当該オブジェクトが未登録であれば生成する。
263
+ #
264
+ # @param [String] iri International Resource Identifier
265
+ # @param [String] namespace (デフォルトの名前空間, 指定がないときは名前空間を省略しない)
266
+ # @param [Block] block オブジェクトが見つからない場合の代替処理
267
+ #
268
+ # @return [When::Parts::Resource]
269
+ #
270
+ def _instance(iri, namespace=nil, &block)
271
+ _setup_ unless @_pool
272
+
273
+ case iri
274
+ when Array ; return iri.map {|e| _instance(e, namespace)} # 配列は個別に処理
275
+ when Resource ; return iri # 登録済みはそのまま
276
+ when String ; # 解析処理へ
277
+ else ; raise ArgumentError, "can't convert #{iri.class} to String" # 例外
278
+ end
279
+
280
+ # 内部文字列化
281
+ iri = When::EncodingConversion.to_internal_encoding(iri)
282
+
283
+ # 階層がある場合は、階層をたどる
284
+ iri = Resource._decode(iri)
285
+ iri = $1 while iri =~ /\A\((.*)\)\z/
286
+ iri = namespace + iri if namespace && iri !~ IRIHeader
287
+ root, *leaves= Resource._encode(iri).split(/::/)
288
+ if leaves.size > 0
289
+ return leaves.inject(_instance(Resource._decode(root))) {|obj,leaf| obj[Resource._decode(leaf)]}
290
+ end
291
+
292
+ # 登録ずみなら、参照
293
+ iri = _extract_prefix(iri)
294
+ path, query = iri.split(/\?/, 2)
295
+ if When.multi_thread
296
+ my_mutex = nil
297
+ @_lock_.synchronize do
298
+ @_pool ||= {}
299
+ unless @_pool[iri]
300
+ my_mutex = Mutex.new
301
+ @_pool[iri] = my_mutex
302
+ end
303
+ end
304
+ case @_pool[iri]
305
+ when my_mutex; my_mutex.synchronize {@_pool[iri] = _create_object(iri, path, query, &block) }
306
+ when Mutex ; @_pool[iri].synchronize {@_pool[iri]}
307
+ else ; @_pool[iri]
308
+ end
309
+ else
310
+ @_pool ||= {}
311
+ @_pool[iri] ||= _create_object(iri, path, query, &block)
312
+ end
313
+ end
314
+
315
+ # @private
316
+ def _path_with_prefix(obj, simple=true)
317
+ _setup_ unless @_pool
318
+ path = obj.kind_of?(Class) ? obj.to_s.sub(/\AWhen::/, base_uri).gsub(/::/, '/') :
319
+ obj.iri
320
+ simple ? _simplify_path(path) : path
321
+ end
322
+
323
+ # @private
324
+ def _simplify_path(path)
325
+ _prefix_values.each do |value|
326
+ index = path.index(value)
327
+ return _prefix_index[value] + ':' + path[value.length..-1] if index
328
+ end
329
+ return path
330
+ end
331
+
332
+ # @private
333
+ def _parse(line, type=nil)
334
+ return line unless line.kind_of?(String)
335
+ line.sub!(/\s#.*\z/, '')
336
+ return When::Locale._split($1) if type && /\A#{type}:(.+)\z/i =~ line
337
+ tokens = line.scan(/((?:[^\\:]|\\.)+)(?::(?!\z))?|:/).flatten
338
+ return When::Locale._split(line) unless tokens.size > 1 && /\A(\*)?([A-Z][-A-Z_]{0,255})(?:;(.+))?\z/i =~ tokens[0]
339
+ marked, key, property = $~[1..3]
340
+ values = tokens[1..-1]
341
+ value = values.join(':') unless values == [nil]
342
+ content = ContentLine.new(key, value, marked)
343
+ value ||= ''
344
+ if property
345
+ content.attribute['.'] = property + ':' + value
346
+ property.scan(/((?:[^\\;]|\\.)+)(?:;(?!\z))?|;/).flatten.each do |pr|
347
+ pr ||= ''
348
+ pr.gsub!(/\\./) {|escape| ContentLine::RFC6350[escape] || escape}
349
+ prop = ContentLine.new(*pr.split(/=/, 2))
350
+ content.attribute[prop.predicate] = prop
351
+ end
352
+ else
353
+ content.attribute['.'] = value
354
+ end
355
+ return content
356
+ end
357
+
358
+ # @private
359
+ def _extract_prefix(path, capitalize=false)
360
+ if (path =~ /\A(.+?):+(.+)\z/)
361
+ prefix, klass = $~[1..2]
362
+ if capitalize
363
+ prefix = '_' + prefix.downcase
364
+ klass = klass.capitalize if klass == klass.upcase
365
+ end
366
+ path = _prefix[prefix] + klass if _prefix[prefix]
367
+ elsif capitalize && path =~ /\A(v[^\/]+|daylight$|standard$)/i
368
+ klass = path.sub(/\Av/i, '').capitalize
369
+ path = _prefix['_v'] + klass if When::V.const_defined?(klass) &&
370
+ When::V.const_get(klass).kind_of?(Class)
371
+ end
372
+ return path
373
+ end
374
+
375
+ # @private
376
+ def _replace_tags(source, tags)
377
+ case source
378
+ when When::BasicTypes::M17n
379
+ source
380
+ when String
381
+ target = source.dup
382
+ tags.each_pair do |key, value|
383
+ target.gsub!(/#\{([?&][^=#}]+?=)?#{key}(:.*?)?\}/, '\1' + value) if value.kind_of?(String)
384
+ end
385
+ target.gsub(/#\{.+?(:(.*?))?\}/, '\2')
386
+ when Array
387
+ source.map {|target| _replace_tags(target, tags)}
388
+ when Hash
389
+ target = {}
390
+ source.each_pair do |key, value|
391
+ target[key] = _replace_tags(tags[key] || value, tags)
392
+ end
393
+ target
394
+ else
395
+ source
396
+ end
397
+ end
398
+
399
+ # @private
400
+ def _encode(iri)
401
+ return iri unless iri =~ /\(/
402
+
403
+ iri = iri.dup
404
+ begin
405
+ unless iri.gsub!(/\([^()]*\)/) {|token|
406
+ token.gsub(/[():?&%]/) {|char|'%' + char.ord.to_s(16)}
407
+ }
408
+ raise ArgumentError, 'Brackets do not correspond: ' + iri
409
+ end
410
+ end while iri =~ /\(/
411
+ iri
412
+ end
413
+
414
+ # @private
415
+ def _decode(iri)
416
+ return iri unless iri =~ /%28/
417
+
418
+ iri = iri.dup
419
+ begin
420
+ unless iri.gsub!(/%28.*?%29/) {|token|
421
+ token.gsub(/%([\dA-F]{2})/i) {$1.to_i(16).chr}
422
+ }
423
+ raise ArgumentError, 'Brackets do not correspond: ' + iri
424
+ end
425
+ end while iri =~ /%28/
426
+ iri = $1 if iri =~ /\A\((.*)\)\z/
427
+ iri
428
+ end
429
+
430
+ # @private
431
+ def _instantiate(resource)
432
+ return resource unless resource.kind_of?(Array)
433
+ return resource[0].new(*resource[1..-1]) if resource[0].kind_of?(Class)
434
+ return resource.map {|rsc| _instantiate(rsc)}
435
+ end
436
+
437
+ private
438
+
439
+ # オブジェクト生成
440
+ def _create_object(iri, path, query, &block)
441
+ # query analyzation
442
+ options = {}
443
+ replace = {}
444
+ if query
445
+ options = Hash[*Resource._encode(query).split(/&/).map{|pair|
446
+ key, value = pair.split(/=/, 2)
447
+ [key, Resource._decode(value)]
448
+ }.flatten]
449
+ keys = options.keys
450
+ keys.each do |key|
451
+ replace[$1] = options.delete(key) if key =~ /\A([A-Z].*)/
452
+ end
453
+ end
454
+ options['..'] = iri
455
+
456
+ # internal Resource
457
+ if path.index(Resource.base_uri) == 0
458
+ list = _class(path)
459
+ if list
460
+ return _internal(list, replace, options)
461
+ else
462
+ object = yield(iri) if block_given?
463
+ return object if object
464
+ raise IOError, 'IRI not found - ' + path
465
+ end
466
+ end
467
+
468
+ # external Resource
469
+ begin
470
+ object = When::Locale.send(:wikipedia_object, path, {:query=>query})
471
+ return object if object
472
+ OpenURI
473
+ args = [path, "1".respond_to?(:force_encoding) ? 'r:utf-8' : 'r']
474
+ args << {:ssl_verify_mode=>OpenSSL::SSL::VERIFY_NONE} if path =~ /\Ahttps:/
475
+ open(*args) do |file|
476
+ resource = file.read
477
+ case resource[0..5].upcase
478
+ when 'BEGIN:'
479
+ options['.'] = _ics(_replace_tags(resource, replace).split(/[\n\r]+/))
480
+ options['.'][0].new(options)
481
+ when '<?XML '
482
+ options['.'] = _xml(REXML::Document.new(_replace_tags(resource, replace)).root)
483
+ options['.'][0].new(options)
484
+ else
485
+ raise NoMethodError, 'JSON not supported' unless Object.const_defined?(:JSON)
486
+ _internal(_json([JSON.parse(resource)]), replace, options)
487
+ end
488
+ end
489
+ rescue OpenURI::HTTPError => error
490
+ message = error.message + " - #{path}"
491
+ error = error.respond_to?(:uri) ?
492
+ error.class.new(message, error.io, error.uri) :
493
+ error.class.new(message, error.io)
494
+ raise error
495
+ end
496
+ end
497
+
498
+ # 指定のモジュール/クラスで直接定数が定義されているか
499
+ if method(:const_defined?).arity == 1
500
+ def _directry_defined?(klass, const)
501
+ klass.const_defined?(const)
502
+ end
503
+ else
504
+ def _directry_defined?(klass, const)
505
+ klass.const_defined?(const, false)
506
+ end
507
+ end
508
+
509
+ # 内部形式定義の取得
510
+ def _class(path)
511
+ list = [When]
512
+ path[Resource.base_uri.length..-1].split(/\//).each do |mod|
513
+ if list[0].kind_of?(Module) && _directry_defined?(list[0], mod)
514
+ list.unshift(list[0].const_get(mod))
515
+ else
516
+ return nil unless list[0] == When::V
517
+ list.unshift(When::V::Root)
518
+ return list
519
+ end
520
+ end
521
+ return list
522
+ end
523
+
524
+ # 内部形式定義のオブジェクト化
525
+ def _internal(list, replace, options)
526
+ case list[0]
527
+ when Class
528
+ list[0].new(options)
529
+ when Array
530
+ top = list[0][0]
531
+ if top.kind_of?(Hash)
532
+ top.each_pair do |key, value|
533
+ replace.update(value[replace[key]]) if value.kind_of?(Hash) && value[replace[key]]
534
+ end
535
+ list[0] = list[0][1..-1]
536
+ list[0] = _replace_tags(list[0], top.merge(replace))
537
+ end
538
+ if list[0][0].kind_of?(Class)
539
+ # 配列の先頭がクラスである場合
540
+ klass, *list = list[0]
541
+ unless list[-1].kind_of?(Hash)
542
+ if list.length == 1
543
+ list[0] = {'.'=>Array(list[0])}
544
+ else
545
+ list << {}
546
+ end
547
+ end
548
+ else
549
+ # 配列の先頭がクラスではない場合
550
+ klass, *list = [list[1], *list[0]]
551
+ list << {} unless list[-1].kind_of?(Hash)
552
+ end
553
+ list[-1] = list[-1].merge(options)
554
+ klass.new(*list)
555
+ else
556
+ list[0]
557
+ end
558
+ end
559
+
560
+ # .xml フォーマットの読み込み
561
+ def _xml(xml, namespace={})
562
+ obj = [_class(_extract_prefix(xml.attributes['type'].to_s))[0]]
563
+ xml.attributes.each_pair do |key,value|
564
+ expanded_name = value.expanded_name
565
+ next unless (expanded_name =~ /\Axmlns/)
566
+ key = '' if expanded_name == 'xmlns'
567
+ namespace[key] = value.to_s
568
+ end
569
+ obj << ContentLine.new('xmlns:namespace', namespace) if (namespace.size>0)
570
+ xml.each do |e|
571
+ next unless defined? e.name
572
+ if (e.attributes['type'])
573
+ obj << _xml(e, namespace)
574
+ else
575
+ content = ContentLine.new(e.expanded_name, e.attributes['ref']||e.text)
576
+ e.attributes.each_pair do |key,value|
577
+ attr = ContentLine.new(value.name, value)
578
+ attr.namespace = value.prefix
579
+ content.attribute[key] = attr
580
+ end
581
+ obj << content
582
+ end
583
+ end
584
+ return obj
585
+ end
586
+
587
+ # .ics フォーマットの読み込み
588
+ def _ics(ics, type=nil)
589
+ obj = [type] if type
590
+ indent = nil
591
+ while (line = ics.shift) do
592
+ line.chomp!
593
+ case line
594
+ when /\A\s*BEGIN:(.*)\z/
595
+ if type
596
+ subtype = $1
597
+ obj[-1] = _parse(obj[-1], type) if obj.length > 1
598
+ obj << _ics(ics, subtype)
599
+ else
600
+ type = $1
601
+ obj = [type]
602
+ end
603
+ when /\A\s*END:(.*)\z/
604
+ raise TypeError, "Irregal Type : #{$1}" unless type == $1
605
+ obj[0] = _class(_extract_prefix(type, true))[0]
606
+ obj[-1] = _parse(obj[-1], type)
607
+ return obj
608
+ when /\A\s*#/
609
+ when /\A(\s*)(.*)\z/
610
+ indent = $1 unless indent
611
+ if indent.length < $1.length
612
+ obj[-1] += line[(indent.length+1)..-1] # See RFC5545 3.1 Content Lines
613
+ else
614
+ obj << $2
615
+ obj[-2] = _parse(obj[-2], type)
616
+ end
617
+ end
618
+ end
619
+ raise ArgumentError, "BEGIN-END mismatch"
620
+ end
621
+
622
+ # .json フォーマットの読み込み
623
+ def _json(json)
624
+ case json
625
+ when Array
626
+ json.map {|value| _json(value)}
627
+ when Hash
628
+ hash = {}
629
+ json.each_pair {|key, value| hash[key] = _json(value)}
630
+ hash
631
+ when String
632
+ return json unless json =~ /\AWhen::/
633
+ begin
634
+ return json.split('::').inject(Object) {|ns, sym| ns.const_get(sym)}
635
+ rescue
636
+ json
637
+ end
638
+ else
639
+ json
640
+ end
641
+ end
642
+ end
643
+
644
+ include Synchronize
645
+
646
+ # @private
647
+ attr_reader :_pool
648
+
649
+ # self が has-a 関係で包含するオブジェクト
650
+ #
651
+ # @return [Array<When::Parts::Resource>]
652
+ #
653
+ attr_accessor :child
654
+ private :child=
655
+
656
+ #
657
+ # Resource包含階層で使用する namespace
658
+ #
659
+ # When::BasicTypes::M17n の生成に使用する namespace を定義する。
660
+ # RFC 5545 に対する拡張である。
661
+ # xml で記述する場合には、本ライブラリ以外でも namespace を定義している。
662
+ #
663
+ # @return [Hash] { prefix => prefix文字列 }
664
+ #
665
+ attr_reader :namespace
666
+
667
+ #
668
+ # Resource包含階層で使用する locale
669
+ #
670
+ # When::BasicTypes::M17n の生成に使用する locale を定義する。
671
+ # RFC 5545 に対する拡張である。
672
+ #
673
+ # @return [Array<String>]
674
+ #
675
+ attr_reader :locale
676
+
677
+ # strftime で有効な locale
678
+ #
679
+ # @return [Array<String>]
680
+ #
681
+ attr_reader :keys
682
+
683
+ # オブジェクトの IRI
684
+ #
685
+ # @param [Boolean] prefix true ならIRI の先頭部分を簡約表現にする
686
+ #
687
+ # @return [Sring]
688
+ #
689
+ def iri(prefix=false)
690
+ unless @iri
691
+ root = @_pool['..']
692
+ path = root.instance_of?(String) ? root : label.to_s
693
+ if root.respond_to?(:iri)
694
+ root_iri = root.iri
695
+ path = root_iri + '::' + path if root_iri
696
+ end
697
+ @iri = path
698
+ end
699
+ prefix ? Resource._simplify_path(@iri) : @iri
700
+ end
701
+
702
+ # IRI または child の番号によるオブジェクト参照
703
+ #
704
+ # @param [String] iri オブジェクトの IRI
705
+ # @param [Numeric] iri child の index
706
+ #
707
+ # @return [When::parts::Resource]
708
+ #
709
+ def [](iri)
710
+ case iri
711
+ when Numeric
712
+ return child[iri * 1]
713
+ when String
714
+ obj = self
715
+ Resource._encode(iri).split(/::/).each do |label|
716
+ return obj.child if label == '*'
717
+ if obj == Resource
718
+ obj = Resource._instance(Resource._decode(label))
719
+ else
720
+ case label
721
+ when '' ; obj = Resource
722
+ when '.' # obj = obj
723
+ else ; obj = obj._pool[Resource._decode(label)]
724
+ end
725
+ end
726
+ raise ArgumentError, "IRI not found: #{iri}" unless obj
727
+ end
728
+ return obj
729
+ else
730
+ super(iri)
731
+ #raise ArgumentError, "IRI not found: #{iri}"
732
+ end
733
+ end
734
+
735
+ # self を直接に包含するオブジェクト
736
+ #
737
+ # @return [When::Parts::Resource]
738
+ #
739
+ def parent
740
+ @_pool['..'].kind_of?(Resource) ? @_pool['..'] : nil
741
+ end
742
+
743
+ # self を包含するオブジェクト階層
744
+ #
745
+ # @param [Class] klass 階層を遡るクラス
746
+ #
747
+ # @return [When::Parts::Resource]
748
+ #
749
+ def hierarchy(klass=self.class)
750
+ hierarchy = []
751
+ parent = self
752
+ while parent.kind_of?(klass)
753
+ hierarchy << parent
754
+ parent = parent.parent
755
+ end
756
+ hierarchy.reverse
757
+ end
758
+
759
+ # self が other を包含するか
760
+ #
761
+ # @return [Boolean]
762
+ # [ true - 包含する ]
763
+ # [ false - 包含しない ]
764
+ #
765
+ def include?(other)
766
+ c = other
767
+ while c.kind_of?(Resource)
768
+ return true if c.equal?(self)
769
+ c = c.parent
770
+ end
771
+ return false
772
+ end
773
+
774
+ # other が self を包含するか
775
+ #
776
+ # @return [Boolean]
777
+ # [ true - 包含される ]
778
+ # [ false - 包含されない ]
779
+ #
780
+ def included?(other)
781
+ other.include?(self)
782
+ end
783
+
784
+ # 前のオブジェクト
785
+ #
786
+ # @return [When::Parts::Resource]
787
+ #
788
+ def prev
789
+ c = self
790
+ c = c.child[0] while c.child && c.child.size > 0
791
+ c = c._pool['.<-']
792
+ c = c.child[-1] while c && c.child && c.child.size > 0
793
+ c
794
+ end
795
+
796
+ # 次のオブジェクト
797
+ #
798
+ # @return [When::Parts::Resource]
799
+ #
800
+ def next
801
+ c = self
802
+ c = c.child[0] while c.child && c.child.size > 0
803
+ c._pool['.->']
804
+ end
805
+ alias :succ :next
806
+
807
+ # オブジェクト包含階層の末端か?
808
+ #
809
+ # @return [Boolean]
810
+ # [ true - IRIが付与された他のオブジェクトを包含していない ]
811
+ # [ false - IRIが付与された他のオブジェクトを包含している ]
812
+ #
813
+ def leaf?
814
+ !@child || (@child.length==0)
815
+ end
816
+
817
+ # IRIが付与されているか?
818
+ #
819
+ # @return [Boolean]
820
+ # [ true - IRIが付与されている ]
821
+ # [ false - IRIが付与されていない ]
822
+ #
823
+ def registered?
824
+ leaf = self
825
+ while leaf._pool['..'].respond_to?(:_pool)
826
+ root = leaf._pool['..']
827
+ return false unless leaf.equal?(root._pool[leaf.label])
828
+ leaf = root
829
+ end
830
+ Resource._pool.value?(leaf)
831
+ end
832
+
833
+ # When::BasicTypes::M17n の生成/参照
834
+ #
835
+ # @param [When::BasicTypes::M17n] source 処理を行わず、そのままsourceを返す
836
+ # @param [String] source locale と 文字列の対応
837
+ # @param [Array] source 要素を個別に解釈して生成したオブジェクトのArrayを返す
838
+ # @param [Hash] namespace prefix の指定
839
+ # @param [Array] locale locale の定義順序の指定
840
+ # @param [Hash] options (see {When::BasicTypes::M17n.new}[link:When/BasicTypes/M17n.html#method-c-new])
841
+ #
842
+ # @return [When::BasicTypes::M17n or Array<them>]
843
+ #
844
+ def m17n(source, namespace=nil, locale=nil, options={})
845
+ case source
846
+ when Array ; When::BasicTypes::M17n.new(source, namespace, locale, options)
847
+ when When::BasicTypes::M17n ; source
848
+ when String
849
+ return self[$1] if source =~ /\A\s*\[((\.{1,2}|::)+[^\]]+)\]/
850
+ When::BasicTypes::M17n.new(source, namespace, locale, options)
851
+ else ; raise TypeError, "Invalid Type: #{source.class}"
852
+ end
853
+ end
854
+
855
+ # 子オブジェクトを順に取り出す enumerator
856
+ #
857
+ def enum_for
858
+ @child.enum_for
859
+ end
860
+ alias :to_enum :enum_for
861
+
862
+ # Enumerator 生成のダミー
863
+ #
864
+ # @param [Object] other
865
+ #
866
+ # @return [Enumerator]
867
+ #
868
+ def ^(other)
869
+ return nil
870
+ end
871
+
872
+ # 順次実行
873
+ #
874
+ # @param [Array] args 各サブクラスの enum_for にそのまま渡す
875
+ # @param [Block] block 実行するブロック
876
+ #
877
+ # @return [Enumerator]
878
+ #
879
+ def each(*args, &block)
880
+ enum = enum_for(*args)
881
+ return enum unless block
882
+ enum.each(&block)
883
+ end
884
+
885
+ # map, collect の再定義
886
+ #
887
+ # has-a 関係の子 Resource に対して map/collect を行う
888
+ #
889
+ # @param [Block] block 実行するブロック
890
+ #
891
+ # @return [Array]
892
+ #
893
+ def map(&block)
894
+ @child.map(&block)
895
+ end
896
+ alias :collect :map
897
+
898
+ protected
899
+
900
+ # @private
901
+ def pool_keys
902
+ _pool.keys
903
+ end
904
+
905
+ private
906
+
907
+ # 属性の設定
908
+ def _attributes(args)
909
+ options =_get_options(args)
910
+ _set_variables(options)
911
+ return args, options
912
+ end
913
+
914
+ # option の読み出し
915
+ def _get_options(args)
916
+ options = args[-1].kind_of?(Hash) ? args.pop : {}
917
+ @_pool = {}
918
+ @_pool['..'] = options['..'] if (options['..'])
919
+
920
+ # 配下のオブジェクトの生成と関連付け
921
+ if (options.key?('.'))
922
+ _child(options)
923
+ end
924
+
925
+ options
926
+ end
927
+
928
+ # 変数の設定
929
+ def _set_variables(options)
930
+ @options = options[:options] || {} if options.key?(:options)
931
+ options.each_pair do |key,value|
932
+ unless (key =~ /\Aoptions$|^\.|^[A-Z]/)
933
+ case "#{key}"
934
+ when 'namespace' ; value = When::Locale._namespace(value)
935
+ when 'locale' ; value = When::Locale._locale(value)
936
+ end
937
+ instance_variable_set("@#{key}", value)
938
+ end
939
+ end
940
+ end
941
+
942
+ # 配下のオブジェクトの生成
943
+ def _child(options)
944
+ @child = []
945
+ query = options.dup
946
+ options['..'] = self
947
+ leaves = options.delete('.').map {|leaf| Resource._parse(leaf)}
948
+ key_list = []
949
+ properties = {}
950
+ label_candidates = nil
951
+
952
+ # ContentLine の処理(namespace, locale, altidの前処理)
953
+ leaves.each do |content|
954
+ next unless content.kind_of?(ContentLine)
955
+ key = content.predicate
956
+ value = content.object
957
+ next options.delete(key) unless value
958
+ case key
959
+ when 'namespace'
960
+ options[key] ||= {}
961
+ if content.attribute['altid']
962
+ options[key][content.attribute['prefix'].object] = content.object
963
+ else
964
+ options[key] = options[key].update(When::Locale._namespace(value))
965
+ end
966
+ when 'locale'
967
+ options[key] = When::Locale._locale(value)
968
+ else
969
+ _parse_altid(properties, content)
970
+ key_list << key unless key_list.include?(key)
971
+ end
972
+ end
973
+
974
+ # ContentLine の処理(一般)
975
+ key_list.each do |key|
976
+ content = properties[key][0]
977
+ value = content.same_altid ? When::BasicTypes::M17n.new(content, options['namespace'], []) : content.object
978
+ value = When::BasicTypes::M17n.new(value, nil, nil, options) if value.instance_of?(String) && value =~ /\A\s*\[/
979
+ @_pool[value.to_s] = value if value.kind_of?(When::BasicTypes::M17n)
980
+ if content.marked || key == self.class::LabelProperty
981
+ @label = value
982
+ else
983
+ options[key] = value
984
+ label_candidates ||= value
985
+ end
986
+ end
987
+
988
+ # Array の処理(子オブジェクトの生成)
989
+ leaves.each do |content|
990
+ next unless content.kind_of?(Array)
991
+ if content[0].kind_of?(Class)
992
+ list = []
993
+ content.each do |e|
994
+ if e.kind_of?(Hash)
995
+ list += e.keys.map {|key| Resource::ContentLine.new(key, e[key])}
996
+ else
997
+ list << e
998
+ end
999
+ end
1000
+ options['.'] = list
1001
+ @child << content[0].new(options.dup)
1002
+ else
1003
+ options.delete('.')
1004
+ @child << self.class.new(*(content + [options]))
1005
+ end
1006
+ end
1007
+
1008
+ # 代表ラベルの設定
1009
+ options.update(query)
1010
+ unless @label
1011
+ raise ArgumentError, "label attribute not found: #{options['.']}" unless label_candidates
1012
+ @label = label_candidates
1013
+ end
1014
+ end
1015
+
1016
+ # ALTIDを持つ ContentLine の解析
1017
+ def _parse_altid(properties, content)
1018
+ key = content.predicate
1019
+ properties[key] ||= []
1020
+ if content.attribute['altid']
1021
+ found = false
1022
+ (0...properties[key].length).to_a.each do |i|
1023
+ prev = properties[key][i]
1024
+ if prev.attribute['altid'] && prev.attribute['altid'].object == content.attribute['altid'].object
1025
+ content.same_altid = prev
1026
+ properties[key][i] = content
1027
+ found = true
1028
+ break
1029
+ end
1030
+ end
1031
+ end
1032
+ properties[key] << content unless found
1033
+ end
1034
+
1035
+ # 配下のオブジェクトの前後関係の設定
1036
+ def _sequence
1037
+ return unless @child
1038
+ prev = @_pool['..'].child[-1] if @_pool['..'].respond_to?(:child)
1039
+ @child.each do |v|
1040
+ if prev
1041
+ v._pool['.<-'] = prev
1042
+ prev._pool['.->'] = v
1043
+ while (prev.child && prev.child[-1]) do
1044
+ prev = prev.child[-1]
1045
+ prev._pool['.->'] = v
1046
+ end
1047
+ end
1048
+ @_pool[v.label.to_s] = v
1049
+ prev = v
1050
+ end
1051
+ end
1052
+
1053
+ alias :__method_missing :method_missing
1054
+
1055
+ # その他のメソッド
1056
+ # When::Parts::Resource で定義されていないメソッドは
1057
+ # 処理を @child (type: Array) に委譲する
1058
+ #
1059
+ def method_missing(name, *args, &block)
1060
+ return __method_missing(name, *args, &block) if When::Parts::MethodCash::Escape.key?(name)
1061
+ self.class.module_eval %Q{
1062
+ def #{name}(*args, &block)
1063
+ @child.send("#{name}", *args, &block)
1064
+ end
1065
+ } unless When::Parts::MethodCash.escape(name)
1066
+ @child.send(name, *args, &block)
1067
+ end
1068
+ end
1069
+ end