kch-ya2yaml 0.29.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README +114 -0
- data/lib/ya2yaml.rb +371 -0
- data/test/t.gif +0 -0
- data/test/t.yaml +5 -0
- data/test/test.rb +413 -0
- metadata +62 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2006 Akira FUNAI <funai.akira@gmail.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
copy of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
8
|
+
Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
= Ya2YAML - An UTF8 safe YAML dumper
|
2
|
+
|
3
|
+
Project Contact: Akira FUNAI <mailto:funai.akira@gmail.com>
|
4
|
+
|
5
|
+
Ya2YAML is "yet another to_yaml". It emits YAML document with complete UTF8 support (string/binary detection, "\u" escape sequences and Unicode specific line breaks). I hope someone might find it useful until Syck/RbYAML come out with UTF8/16 support.
|
6
|
+
|
7
|
+
== Usage
|
8
|
+
|
9
|
+
*code*:
|
10
|
+
|
11
|
+
# encoding: UTF-8
|
12
|
+
$KCODE = 'UTF8' unless RUBY_VERSION >= '1.9'
|
13
|
+
require 'ya2yaml'
|
14
|
+
|
15
|
+
obj = [
|
16
|
+
"abc\nxyz\n",
|
17
|
+
"日本語\n文字列\n",
|
18
|
+
"\xfd\xfe\xff",
|
19
|
+
]
|
20
|
+
puts obj.ya2yaml(:syck_compatible => true)
|
21
|
+
|
22
|
+
*output*:
|
23
|
+
|
24
|
+
---
|
25
|
+
- |
|
26
|
+
abc
|
27
|
+
xyz
|
28
|
+
- |
|
29
|
+
日本語
|
30
|
+
文字列
|
31
|
+
- !binary |
|
32
|
+
/f7/
|
33
|
+
|
34
|
+
== Method and Objects
|
35
|
+
|
36
|
+
When you require 'ya2yaml', public method 'Object#ya2yaml' is defined. Currently these Objects are supported.
|
37
|
+
|
38
|
+
as YAML generic types:
|
39
|
+
- Array
|
40
|
+
- Hash
|
41
|
+
- NilClass
|
42
|
+
- String
|
43
|
+
- TrueClass
|
44
|
+
- FalseClass
|
45
|
+
- Fixnum
|
46
|
+
- Bignum
|
47
|
+
- Float
|
48
|
+
- Date
|
49
|
+
- Time
|
50
|
+
|
51
|
+
as Ruby specific types:
|
52
|
+
- Symbol
|
53
|
+
- Range
|
54
|
+
- Regexp
|
55
|
+
- Struct
|
56
|
+
- everything else as a generic Object (serializes instance variables)
|
57
|
+
|
58
|
+
A String which contains any non-UTF8 character will be regarded as "binary" and encoded in BASE64.
|
59
|
+
|
60
|
+
== Options
|
61
|
+
|
62
|
+
# set them individually
|
63
|
+
obj.ya2yaml(
|
64
|
+
:indent_size => 4,
|
65
|
+
:hash_order => ['name','age','address'],
|
66
|
+
:minimum_block_length => 16,
|
67
|
+
:printable_with_syck => true,
|
68
|
+
:escape_b_specific => true,
|
69
|
+
:escape_as_utf8 => true
|
70
|
+
)
|
71
|
+
|
72
|
+
# or simply set this for a safe roundtrip with Syck.
|
73
|
+
obj.ya2yaml(:syck_compatible => true)
|
74
|
+
|
75
|
+
**CAUTION** Some of these options are to avoid compatibility issue with Syck. When you set these options to false, the emitted YAML document might not be loadable with YAML.load() although the syntax is valid.
|
76
|
+
|
77
|
+
- :indent_size
|
78
|
+
- default: 2
|
79
|
+
- Number of indentation spaces for a level.
|
80
|
+
|
81
|
+
- :hash_order
|
82
|
+
- default: nil
|
83
|
+
- Array of hash keys indicating sort order (this option only works on a top-level hash).
|
84
|
+
|
85
|
+
- :minimum_block_length
|
86
|
+
- default: 0
|
87
|
+
- Minimum length of a string emitted in block scalar style. If a string is shorter than this value, it will be emitted in one line.
|
88
|
+
|
89
|
+
- :printable_with_syck
|
90
|
+
- default: false
|
91
|
+
- When set to true, Ya2YAML will regard some kind of strings (which cause Syck roundtrip failures) as "non-printable" and double-quote them.
|
92
|
+
|
93
|
+
- :escape_b_specific
|
94
|
+
- default: false
|
95
|
+
- When set to true, Ya2YAML will regard Unicode specific line breaks (LS and PS) as "non-printable" and escape them.
|
96
|
+
|
97
|
+
- :escape_as_utf8
|
98
|
+
- default: false
|
99
|
+
- When set to true, Ya2YAML uses Ruby-like escape sequences in UTF8 code instead of "\uXXXX" style in UCS code. It also suppresses use of "\L" and "\P" (escape sequences for LS and PS).
|
100
|
+
|
101
|
+
- :syck_compatible
|
102
|
+
- default: false
|
103
|
+
- When set to true, These options are set to true at once. You have to set this to false when you set them individually.
|
104
|
+
- :printable_with_syck
|
105
|
+
- :escape_b_specific
|
106
|
+
- :escape_as_utf8
|
107
|
+
|
108
|
+
== More information
|
109
|
+
|
110
|
+
Visit http://rubyforge.org/projects/ya2yaml
|
111
|
+
|
112
|
+
== License
|
113
|
+
|
114
|
+
Ya2YAML is distributed with a MIT license, which can be found in the file LICENSE.
|
data/lib/ya2yaml.rb
ADDED
@@ -0,0 +1,371 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# $Id: ya2yaml.rb,v 0.29 2009/02/09 09:01:30 funai Exp funai $
|
4
|
+
#
|
5
|
+
# Author:: Akira FUNAI
|
6
|
+
# Copyright:: Copyright (c) 2006 Akira FUNAI
|
7
|
+
# License:: MIT License
|
8
|
+
|
9
|
+
class Ya2YAML
|
10
|
+
|
11
|
+
def initialize(opts = {})
|
12
|
+
options = opts.dup
|
13
|
+
options[:indent_size] = 2 if options[:indent_size].to_i <= 0
|
14
|
+
options[:minimum_block_length] = 0 if options[:minimum_block_length].to_i <= 0
|
15
|
+
options.update(
|
16
|
+
{
|
17
|
+
:printable_with_syck => true,
|
18
|
+
:escape_b_specific => true,
|
19
|
+
:escape_as_utf8 => true,
|
20
|
+
}
|
21
|
+
) if options[:syck_compatible]
|
22
|
+
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def _ya2yaml(obj)
|
27
|
+
throw 'set $KCODE to "UTF8".' if $KCODE != 'UTF8' unless RUBY_VERSION >= '1.9.0'
|
28
|
+
'--- ' + emit(obj,1) + "\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def emit(obj,level)
|
34
|
+
case obj.class.to_s
|
35
|
+
when 'Array'
|
36
|
+
if (obj.length == 0)
|
37
|
+
'[]'
|
38
|
+
else
|
39
|
+
indent = "\n" + s_indent(level - 1)
|
40
|
+
obj.collect {|o|
|
41
|
+
indent + '- ' + emit(o,level + 1)
|
42
|
+
}.join('')
|
43
|
+
end
|
44
|
+
when 'Hash'
|
45
|
+
if (obj.length == 0)
|
46
|
+
'{}'
|
47
|
+
else
|
48
|
+
indent = "\n" + s_indent(level - 1)
|
49
|
+
hash_order = @options[:hash_order]
|
50
|
+
if (hash_order && level == 1)
|
51
|
+
hash_keys = obj.keys.sort {|x,y|
|
52
|
+
x_order = hash_order.index(x) ? hash_order.index(x) : Float::MAX
|
53
|
+
y_order = hash_order.index(y) ? hash_order.index(y) : Float::MAX
|
54
|
+
o = (x_order <=> y_order)
|
55
|
+
(o != 0) ? o : (x.to_s <=> y.to_s)
|
56
|
+
}
|
57
|
+
else
|
58
|
+
hash_keys = obj.keys.sort {|x,y| x.to_s <=> y.to_s }
|
59
|
+
end
|
60
|
+
hash_keys.collect {|k|
|
61
|
+
key = emit(k,level + 1)
|
62
|
+
if (
|
63
|
+
is_one_plain_line?(key) ||
|
64
|
+
key =~ /\A(#{REX_BOOL}|#{REX_FLOAT}|#{REX_INT}|#{REX_NULL})\z/x
|
65
|
+
)
|
66
|
+
indent + key + ': ' + emit(obj[k],level + 1)
|
67
|
+
else
|
68
|
+
indent + '? ' + key +
|
69
|
+
indent + ': ' + emit(obj[k],level + 1)
|
70
|
+
end
|
71
|
+
}.join('')
|
72
|
+
end
|
73
|
+
when 'NilClass'
|
74
|
+
'~'
|
75
|
+
when 'String'
|
76
|
+
emit_string(obj,level)
|
77
|
+
when 'TrueClass','FalseClass'
|
78
|
+
obj.to_s
|
79
|
+
when 'Fixnum','Bignum','Float'
|
80
|
+
obj.to_s
|
81
|
+
when 'Date'
|
82
|
+
obj.to_s
|
83
|
+
when 'Time'
|
84
|
+
offset = obj.gmtoff
|
85
|
+
off_hm = sprintf(
|
86
|
+
'%+.2d:%.2d',
|
87
|
+
(offset / 3600.0).to_i,
|
88
|
+
(offset % 3600.0) / 60
|
89
|
+
)
|
90
|
+
u_sec = (obj.usec != 0) ? sprintf(".%.6d",obj.usec) : ''
|
91
|
+
obj.strftime("%Y-%m-%d %H:%M:%S#{u_sec} #{off_hm}")
|
92
|
+
when 'Symbol'
|
93
|
+
'!ruby/symbol ' + emit_string(obj.to_s,level)
|
94
|
+
when 'Range'
|
95
|
+
'!ruby/range ' + obj.to_s
|
96
|
+
when 'Regexp'
|
97
|
+
'!ruby/regexp ' + obj.inspect
|
98
|
+
else
|
99
|
+
case
|
100
|
+
when obj.is_a?(Struct)
|
101
|
+
struct_members = {}
|
102
|
+
obj.each_pair{|k,v| struct_members[k.to_s] = v }
|
103
|
+
'!ruby/struct:' + obj.class.to_s.sub(/^(Struct::(.+)|.*)$/,'\2') + ' ' +
|
104
|
+
emit(struct_members,level + 1)
|
105
|
+
else
|
106
|
+
# serialized as a generic object
|
107
|
+
object_members = {}
|
108
|
+
obj.instance_variables.each{|k,v|
|
109
|
+
object_members[k.to_s.sub(/^@/,'')] = obj.instance_variable_get(k)
|
110
|
+
}
|
111
|
+
'!ruby/object:' + obj.class.to_s + ' ' +
|
112
|
+
emit(object_members,level + 1)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def emit_string(str,level)
|
118
|
+
(is_string,is_printable,is_one_line,is_one_plain_line) = string_type(str)
|
119
|
+
if is_string
|
120
|
+
if is_printable
|
121
|
+
if is_one_plain_line
|
122
|
+
emit_simple_string(str,level)
|
123
|
+
else
|
124
|
+
(is_one_line || str.length < @options[:minimum_block_length]) ?
|
125
|
+
emit_quoted_string(str,level) :
|
126
|
+
emit_block_string(str,level)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
emit_quoted_string(str,level)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
emit_base64_binary(str,level)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def emit_simple_string(str,level)
|
137
|
+
str
|
138
|
+
end
|
139
|
+
|
140
|
+
def emit_block_string(str,level)
|
141
|
+
str = normalize_line_break(str)
|
142
|
+
|
143
|
+
indent = s_indent(level)
|
144
|
+
indentation_indicator = (str =~ /\A /) ? indent.size.to_s : ''
|
145
|
+
str =~ /(#{REX_NORMAL_LB}*)\z/
|
146
|
+
chomping_indicator = case $1.length
|
147
|
+
when 0
|
148
|
+
'-'
|
149
|
+
when 1
|
150
|
+
''
|
151
|
+
else
|
152
|
+
'+'
|
153
|
+
end
|
154
|
+
|
155
|
+
str.chomp!
|
156
|
+
str.gsub!(/#{REX_NORMAL_LB}/) {
|
157
|
+
$1 + indent
|
158
|
+
}
|
159
|
+
'|' + indentation_indicator + chomping_indicator + "\n" + indent + str
|
160
|
+
end
|
161
|
+
|
162
|
+
def emit_quoted_string(str,level)
|
163
|
+
str = yaml_escape(normalize_line_break(str))
|
164
|
+
if (str.length < @options[:minimum_block_length])
|
165
|
+
str.gsub!(/#{REX_NORMAL_LB}/) { ESCAPE_SEQ_LB[$1] }
|
166
|
+
else
|
167
|
+
str.gsub!(/#{REX_NORMAL_LB}$/) { ESCAPE_SEQ_LB[$1] }
|
168
|
+
str.gsub!(/(#{REX_NORMAL_LB}+)(.)/) {
|
169
|
+
trail_c = $3
|
170
|
+
$1 + trail_c.sub(/([\t ])/) { ESCAPE_SEQ_WS[$1] }
|
171
|
+
}
|
172
|
+
indent = s_indent(level)
|
173
|
+
str.gsub!(/#{REX_NORMAL_LB}/) {
|
174
|
+
ESCAPE_SEQ_LB[$1] + "\\\n" + indent
|
175
|
+
}
|
176
|
+
end
|
177
|
+
'"' + str + '"'
|
178
|
+
end
|
179
|
+
|
180
|
+
def emit_base64_binary(str,level)
|
181
|
+
indent = "\n" + s_indent(level)
|
182
|
+
base64 = [str].pack('m')
|
183
|
+
'!binary |' + indent + base64.gsub(/\n(?!\z)/,indent)
|
184
|
+
end
|
185
|
+
|
186
|
+
def string_type(str)
|
187
|
+
if str.respond_to?(:valid_encoding?) && !str.valid_encoding?
|
188
|
+
return false,false,false,false
|
189
|
+
end
|
190
|
+
(ucs_codes = str.unpack('U*')) rescue (
|
191
|
+
# ArgumentError -> binary data
|
192
|
+
return false,false,false,false
|
193
|
+
)
|
194
|
+
if (
|
195
|
+
@options[:printable_with_syck] &&
|
196
|
+
str =~ /\A#{REX_ANY_LB}* | #{REX_ANY_LB}*\z|#{REX_ANY_LB}{2}\z/
|
197
|
+
)
|
198
|
+
# detour Syck bug
|
199
|
+
return true,false,nil,false
|
200
|
+
end
|
201
|
+
ucs_codes.each {|ucs_code|
|
202
|
+
return true,false,nil,false unless is_printable?(ucs_code)
|
203
|
+
}
|
204
|
+
return true,true,is_one_line?(str),is_one_plain_line?(str)
|
205
|
+
end
|
206
|
+
|
207
|
+
def is_printable?(ucs_code)
|
208
|
+
# YAML 1.1 / 4.1.1.
|
209
|
+
(
|
210
|
+
[0x09,0x0a,0x0d,0x85].include?(ucs_code) ||
|
211
|
+
(ucs_code <= 0x7e && ucs_code >= 0x20) ||
|
212
|
+
(ucs_code <= 0xd7ff && ucs_code >= 0xa0) ||
|
213
|
+
(ucs_code <= 0xfffd && ucs_code >= 0xe000) ||
|
214
|
+
(ucs_code <= 0x10ffff && ucs_code >= 0x10000)
|
215
|
+
) &&
|
216
|
+
!(
|
217
|
+
# treat LS/PS as non-printable characters
|
218
|
+
@options[:escape_b_specific] &&
|
219
|
+
(ucs_code == 0x2028 || ucs_code == 0x2029)
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
def is_one_line?(str)
|
224
|
+
str !~ /#{REX_ANY_LB}(?!\z)/
|
225
|
+
end
|
226
|
+
|
227
|
+
def is_one_plain_line?(str)
|
228
|
+
# YAML 1.1 / 4.6.11.
|
229
|
+
str !~ /^([\-\?:,\[\]\{\}\#&\*!\|>'"%@`\s]|---|\.\.\.)/ &&
|
230
|
+
str !~ /[:\#\s\[\]\{\},]/ &&
|
231
|
+
str !~ /#{REX_ANY_LB}/ &&
|
232
|
+
str !~ /^(#{REX_BOOL}|#{REX_FLOAT}|#{REX_INT}|#{REX_MERGE}
|
233
|
+
|#{REX_NULL}|#{REX_TIMESTAMP}|#{REX_VALUE})$/x
|
234
|
+
end
|
235
|
+
|
236
|
+
def s_indent(level)
|
237
|
+
# YAML 1.1 / 4.2.2.
|
238
|
+
' ' * (level * @options[:indent_size])
|
239
|
+
end
|
240
|
+
|
241
|
+
def normalize_line_break(str)
|
242
|
+
# YAML 1.1 / 4.1.4.
|
243
|
+
str.gsub(/(#{REX_CRLF}|#{REX_CR}|#{REX_NEL})/,"\n")
|
244
|
+
end
|
245
|
+
|
246
|
+
def yaml_escape(str)
|
247
|
+
# YAML 1.1 / 4.1.6.
|
248
|
+
str.gsub(/[^a-zA-Z0-9]/u) {|c|
|
249
|
+
ucs_code, = (c.unpack('U') rescue [??])
|
250
|
+
case
|
251
|
+
when ESCAPE_SEQ[c]
|
252
|
+
ESCAPE_SEQ[c]
|
253
|
+
when is_printable?(ucs_code)
|
254
|
+
c
|
255
|
+
when @options[:escape_as_utf8]
|
256
|
+
c.bytes.collect {|b| '\\x%.2x' % b }.join
|
257
|
+
when ucs_code == 0x2028 || ucs_code == 0x2029
|
258
|
+
ESCAPE_SEQ_LB[c]
|
259
|
+
when ucs_code <= 0x7f
|
260
|
+
sprintf('\\x%.2x',ucs_code)
|
261
|
+
when ucs_code <= 0xffff
|
262
|
+
sprintf('\\u%.4x',ucs_code)
|
263
|
+
else
|
264
|
+
sprintf('\\U%.8x',ucs_code)
|
265
|
+
end
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
module Constants
|
270
|
+
UCS_0X85 = [0x85].pack('U') # c285@UTF8 Unicode next line
|
271
|
+
UCS_0XA0 = [0xa0].pack('U') # c2a0@UTF8 Unicode non-breaking space
|
272
|
+
UCS_0X2028 = [0x2028].pack('U') # e280a8@UTF8 Unicode line separator
|
273
|
+
UCS_0X2029 = [0x2029].pack('U') # e280a9@UTF8 Unicode paragraph separator
|
274
|
+
|
275
|
+
# non-break characters
|
276
|
+
ESCAPE_SEQ = {
|
277
|
+
"\x00" => '\\0',
|
278
|
+
"\x07" => '\\a',
|
279
|
+
"\x08" => '\\b',
|
280
|
+
"\x0b" => '\\v',
|
281
|
+
"\x0c" => '\\f',
|
282
|
+
"\x1b" => '\\e',
|
283
|
+
"\"" => '\\"',
|
284
|
+
"\\" => '\\\\',
|
285
|
+
}
|
286
|
+
|
287
|
+
# non-breaking space
|
288
|
+
ESCAPE_SEQ_NS = {
|
289
|
+
UCS_0XA0 => '\\_',
|
290
|
+
}
|
291
|
+
|
292
|
+
# white spaces
|
293
|
+
ESCAPE_SEQ_WS = {
|
294
|
+
"\x09" => '\\t',
|
295
|
+
" " => '\\x20',
|
296
|
+
}
|
297
|
+
|
298
|
+
# line breaks
|
299
|
+
ESCAPE_SEQ_LB ={
|
300
|
+
"\x0a" => '\\n',
|
301
|
+
"\x0d" => '\\r',
|
302
|
+
UCS_0X85 => '\\N',
|
303
|
+
UCS_0X2028 => '\\L',
|
304
|
+
UCS_0X2029 => '\\P',
|
305
|
+
}
|
306
|
+
|
307
|
+
# regexps for line breaks
|
308
|
+
REX_LF = Regexp.escape("\x0a")
|
309
|
+
REX_CR = Regexp.escape("\x0d")
|
310
|
+
REX_CRLF = Regexp.escape("\x0d\x0a")
|
311
|
+
REX_NEL = Regexp.escape(UCS_0X85)
|
312
|
+
REX_LS = Regexp.escape(UCS_0X2028)
|
313
|
+
REX_PS = Regexp.escape(UCS_0X2029)
|
314
|
+
|
315
|
+
REX_ANY_LB = /(#{REX_LF}|#{REX_CR}|#{REX_NEL}|#{REX_LS}|#{REX_PS})/
|
316
|
+
REX_NORMAL_LB = /(#{REX_LF}|#{REX_LS}|#{REX_PS})/
|
317
|
+
|
318
|
+
# regexps for language-Independent types for YAML1.1
|
319
|
+
REX_BOOL = /
|
320
|
+
y|Y|yes|Yes|YES|n|N|no|No|NO
|
321
|
+
|true|True|TRUE|false|False|FALSE
|
322
|
+
|on|On|ON|off|Off|OFF
|
323
|
+
/x
|
324
|
+
REX_FLOAT = /
|
325
|
+
[-+]?([0-9][0-9_]*)?\.[0-9.]*([eE][-+][0-9]+)? # (base 10)
|
326
|
+
|[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]* # (base 60)
|
327
|
+
|[-+]?\.(inf|Inf|INF) # (infinity)
|
328
|
+
|\.(nan|NaN|NAN) # (not a number)
|
329
|
+
/x
|
330
|
+
REX_INT = /
|
331
|
+
[-+]?0b[0-1_]+ # (base 2)
|
332
|
+
|[-+]?0[0-7_]+ # (base 8)
|
333
|
+
|[-+]?(0|[1-9][0-9_]*) # (base 10)
|
334
|
+
|[-+]?0x[0-9a-fA-F_]+ # (base 16)
|
335
|
+
|[-+]?[1-9][0-9_]*(:[0-5]?[0-9])+ # (base 60)
|
336
|
+
/x
|
337
|
+
REX_MERGE = /
|
338
|
+
<<
|
339
|
+
/x
|
340
|
+
REX_NULL = /
|
341
|
+
~ # (canonical)
|
342
|
+
|null|Null|NULL # (English)
|
343
|
+
| # (Empty)
|
344
|
+
/x
|
345
|
+
REX_TIMESTAMP = /
|
346
|
+
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] # (ymd)
|
347
|
+
|[0-9][0-9][0-9][0-9] # (year)
|
348
|
+
-[0-9][0-9]? # (month)
|
349
|
+
-[0-9][0-9]? # (day)
|
350
|
+
([Tt]|[ \t]+)[0-9][0-9]? # (hour)
|
351
|
+
:[0-9][0-9] # (minute)
|
352
|
+
:[0-9][0-9] # (second)
|
353
|
+
(\.[0-9]*)? # (fraction)
|
354
|
+
(([ \t]*)Z|[-+][0-9][0-9]?(:[0-9][0-9])?)? # (time zone)
|
355
|
+
/x
|
356
|
+
REX_VALUE = /
|
357
|
+
=
|
358
|
+
/x
|
359
|
+
end
|
360
|
+
|
361
|
+
include Constants
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
class Object
|
366
|
+
def ya2yaml(options = {})
|
367
|
+
Ya2YAML.new(options)._ya2yaml(self)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
__END__
|
data/test/t.gif
ADDED
Binary file
|
data/test/test.rb
ADDED
@@ -0,0 +1,413 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
# $Id: test.rb,v 1.6 2009/02/09 09:00:57 funai Exp funai $
|
5
|
+
|
6
|
+
$:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
|
7
|
+
|
8
|
+
Dir.chdir(File.dirname(__FILE__))
|
9
|
+
|
10
|
+
$KCODE = 'UTF8' if RUBY_VERSION < '1.9.0'
|
11
|
+
require 'ya2yaml'
|
12
|
+
|
13
|
+
require 'yaml'
|
14
|
+
require 'test/unit'
|
15
|
+
|
16
|
+
# There's an incompatibility in how ruby handles struct dumps
|
17
|
+
# between versions that's beyond our scope.
|
18
|
+
# (One uses strings internally, the other symbols.)
|
19
|
+
# This just enables tests to pass.
|
20
|
+
class << Struct
|
21
|
+
alias yaml_new_without_indifferent_keys yaml_new
|
22
|
+
def yaml_new(klass, tag, val)
|
23
|
+
val.keys.each { |k, v| val[k.to_sym] = val.delete(k) }
|
24
|
+
yaml_new_without_indifferent_keys(klass, tag, val)
|
25
|
+
end
|
26
|
+
end if RUBY_VERSION >= "1.9"
|
27
|
+
|
28
|
+
class TC_Ya2YAML < Test::Unit::TestCase
|
29
|
+
|
30
|
+
@@struct_klass = Struct::new('Foo',:bar,:buz)
|
31
|
+
class Moo
|
32
|
+
attr_accessor :val1,:val2
|
33
|
+
def initialize(val1,val2)
|
34
|
+
@val1 = val1
|
35
|
+
@val2 = val2
|
36
|
+
end
|
37
|
+
def ==(k)
|
38
|
+
(k.class == self.class) &&
|
39
|
+
(k.val1 == self.val1) &&
|
40
|
+
(k.val2 == self.val2)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
puts "test may take minutes. please wait.\n"
|
44
|
+
|
45
|
+
def setup
|
46
|
+
@text = File.open('./t.yaml', 'r') { |f| f.read }
|
47
|
+
@gif = File.open('./t.gif', 'rb') { |f| f.read }
|
48
|
+
@struct = @@struct_klass.new('barbarbar', @@struct_klass.new('baaaar', 12345))
|
49
|
+
@klass = Moo.new('boobooboo', Time.local(2009,2,9,16,44,10))
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_options
|
53
|
+
opt = {:syck_compatible => true}
|
54
|
+
'foobar'.ya2yaml(opt)
|
55
|
+
assert_equal(
|
56
|
+
{:syck_compatible => true},
|
57
|
+
opt,
|
58
|
+
'ya2yaml should not change the option hash'
|
59
|
+
)
|
60
|
+
|
61
|
+
[
|
62
|
+
[
|
63
|
+
{},
|
64
|
+
"--- \n- \"\\u0086\"\n- |-\n a\xe2\x80\xa8 b\xe2\x80\xa9 c\n- |4-\n abc\n xyz\n",
|
65
|
+
],
|
66
|
+
[
|
67
|
+
{:indent_size => 4},
|
68
|
+
"--- \n- \"\\u0086\"\n- |-\n a\xe2\x80\xa8 b\xe2\x80\xa9 c\n- |8-\n abc\n xyz\n",
|
69
|
+
],
|
70
|
+
[
|
71
|
+
{:minimum_block_length => 16},
|
72
|
+
"--- \n- \"\\u0086\"\n- \"a\\Lb\\Pc\"\n- \" abc\\nxyz\"\n",
|
73
|
+
],
|
74
|
+
# [
|
75
|
+
# {:emit_c_document_end => true},
|
76
|
+
# "--- \n- \"\\u0086\"\n- |-\n a\xe2\x80\xa8 b\xe2\x80\xa9 c\n- |4-\n abc\n xyz\n...\n",
|
77
|
+
# ],
|
78
|
+
[
|
79
|
+
{:printable_with_syck => true},
|
80
|
+
"--- \n- \"\\u0086\"\n- |-\n a\xe2\x80\xa8 b\xe2\x80\xa9 c\n- \" abc\\n\\\n xyz\"\n",
|
81
|
+
],
|
82
|
+
[
|
83
|
+
{:escape_b_specific => true},
|
84
|
+
"--- \n- \"\\u0086\"\n- \"a\\Lb\\Pc\"\n- |4-\n abc\n xyz\n",
|
85
|
+
],
|
86
|
+
[
|
87
|
+
{:escape_as_utf8 => true},
|
88
|
+
"--- \n- \"\\xc2\\x86\"\n- |-\n a\xe2\x80\xa8 b\xe2\x80\xa9 c\n- |4-\n abc\n xyz\n",
|
89
|
+
],
|
90
|
+
[
|
91
|
+
{:syck_compatible => true},
|
92
|
+
"--- \n- \"\\xc2\\x86\"\n- \"a\\xe2\\x80\\xa8b\\xe2\\x80\\xa9c\"\n- \" abc\\n\\\n xyz\"\n",
|
93
|
+
],
|
94
|
+
].each {|opt,yaml|
|
95
|
+
y = ["\xc2\x86","a\xe2\x80\xa8b\xe2\x80\xa9c"," abc\nxyz"].ya2yaml(opt)
|
96
|
+
assert_equal(
|
97
|
+
yaml,
|
98
|
+
y,
|
99
|
+
"option #{opt.inspect} should be recognized"
|
100
|
+
)
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_hash_order
|
105
|
+
[
|
106
|
+
[
|
107
|
+
nil,
|
108
|
+
"--- \na: 1\nb: 2\nc: 3\n",
|
109
|
+
],
|
110
|
+
[
|
111
|
+
[],
|
112
|
+
"--- \na: 1\nb: 2\nc: 3\n",
|
113
|
+
],
|
114
|
+
[
|
115
|
+
['c','b','a'],
|
116
|
+
"--- \nc: 3\nb: 2\na: 1\n",
|
117
|
+
],
|
118
|
+
[
|
119
|
+
['b'],
|
120
|
+
"--- \nb: 2\na: 1\nc: 3\n",
|
121
|
+
],
|
122
|
+
].each {|hash_order,yaml|
|
123
|
+
y = {
|
124
|
+
'a' => 1,
|
125
|
+
'c' => 3,
|
126
|
+
'b' => 2,
|
127
|
+
}.ya2yaml(
|
128
|
+
:hash_order => hash_order
|
129
|
+
)
|
130
|
+
assert_equal(
|
131
|
+
yaml,
|
132
|
+
y,
|
133
|
+
'hash order should be kept when :hash_order provided'
|
134
|
+
)
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_normalize_line_breaks
|
139
|
+
[
|
140
|
+
["\n\n\n\n", "--- \"\\n\\n\\n\\n\"\n",],
|
141
|
+
["\r\n\r\n\r\n", "--- \"\\n\\n\\n\"\n",],
|
142
|
+
["\r\n\n\n", "--- \"\\n\\n\\n\"\n",],
|
143
|
+
["\n\r\n\n", "--- \"\\n\\n\\n\"\n",],
|
144
|
+
["\n\n\r\n", "--- \"\\n\\n\\n\"\n",],
|
145
|
+
["\n\n\n\r", "--- \"\\n\\n\\n\\n\"\n",],
|
146
|
+
["\r\r\n\r", "--- \"\\n\\n\\n\"\n",],
|
147
|
+
["\r\r\r\r", "--- \"\\n\\n\\n\\n\"\n",],
|
148
|
+
["\r\xc2\x85\r\n", "--- \"\\n\\n\\n\"\n",],
|
149
|
+
["\r\xe2\x80\xa8\r\n","--- \"\\n\\L\\n\"\n",],
|
150
|
+
["\r\xe2\x80\xa9\r\n","--- \"\\n\\P\\n\"\n",],
|
151
|
+
].each {|src,yaml|
|
152
|
+
y = src.ya2yaml(
|
153
|
+
:minimum_block_length => 16
|
154
|
+
)
|
155
|
+
assert_equal(
|
156
|
+
yaml,
|
157
|
+
y,
|
158
|
+
'line breaks should be normalized to fit the format.'
|
159
|
+
)
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_structs
|
164
|
+
[
|
165
|
+
[Struct.new('Hoge',:foo).new(123),"--- !ruby/struct:Hoge \n foo: 123\n",],
|
166
|
+
[Struct.new(:foo).new(123), "--- !ruby/struct: \n foo: 123\n",],
|
167
|
+
].each {|src,yaml|
|
168
|
+
y = src.ya2yaml()
|
169
|
+
assert_equal(
|
170
|
+
yaml,
|
171
|
+
y,
|
172
|
+
'ruby struct should be serialized properly'
|
173
|
+
)
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_roundtrip_single_byte_char
|
178
|
+
("\x00".."\x7f").each {|c|
|
179
|
+
y = c.ya2yaml()
|
180
|
+
r = YAML.load(y)
|
181
|
+
assert_equal(
|
182
|
+
(c == "\r" ? "\n" : c), # "\r" is normalized as "\n"
|
183
|
+
r,
|
184
|
+
'single byte characters should round-trip properly'
|
185
|
+
)
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_roundtrip_multi_byte_char
|
190
|
+
[ 0x000080, 0x000085, 0x0000a0, 0x0007ff, 0x000800, 0x000fff, 0x001000,
|
191
|
+
0x002028, 0x002029, 0x00cfff, 0x00d000, 0x00d7ff, 0x00e000, 0x00fffd,
|
192
|
+
0x010000, 0x03ffff, 0x040000, 0x0fffff, 0x100000, 0x10ffff,
|
193
|
+
].each do |ucs_code|
|
194
|
+
[-1, 0, 1].each do |ofs|
|
195
|
+
(c = [ucs_code + ofs].pack('U'))
|
196
|
+
next unless c.valid_encoding? if c.respond_to? :valid_encoding?
|
197
|
+
c_hex = c.unpack('H8')
|
198
|
+
y = c.ya2yaml(
|
199
|
+
:escape_b_specific => true,
|
200
|
+
:escape_as_utf8 => true)
|
201
|
+
r = YAML.load(y)
|
202
|
+
assert_equal(
|
203
|
+
(c == "\xc2\x85" ? "\n" : c), # "\N" is normalized as "\n"
|
204
|
+
r,
|
205
|
+
"multi byte characters #{c_hex} should round-trip properly"
|
206
|
+
)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_roundtrip_ambiguous_string
|
212
|
+
[
|
213
|
+
'true',
|
214
|
+
'false',
|
215
|
+
'TRUE',
|
216
|
+
'FALSE',
|
217
|
+
'Y',
|
218
|
+
'N',
|
219
|
+
'y',
|
220
|
+
'n',
|
221
|
+
'on',
|
222
|
+
'off',
|
223
|
+
true,
|
224
|
+
false,
|
225
|
+
'0b0101',
|
226
|
+
'-0b0101',
|
227
|
+
0b0101,
|
228
|
+
-0b0101,
|
229
|
+
'031',
|
230
|
+
'-031',
|
231
|
+
031,
|
232
|
+
-031,
|
233
|
+
'123.001e-1',
|
234
|
+
'123.01',
|
235
|
+
'123',
|
236
|
+
123.001e-1,
|
237
|
+
123.01,
|
238
|
+
123,
|
239
|
+
'-123.001e-1',
|
240
|
+
'-123.01',
|
241
|
+
'-123',
|
242
|
+
-123.001e-1,
|
243
|
+
-123.01,
|
244
|
+
-123,
|
245
|
+
'INF',
|
246
|
+
'inf',
|
247
|
+
'NaN',
|
248
|
+
'nan',
|
249
|
+
'0xfe2a',
|
250
|
+
'-0xfe2a',
|
251
|
+
0xfe2a,
|
252
|
+
-0xfe2a,
|
253
|
+
'1:23:32.0200',
|
254
|
+
'1:23:32',
|
255
|
+
'-1:23:32.0200',
|
256
|
+
'-1:23:32',
|
257
|
+
'<<',
|
258
|
+
'~',
|
259
|
+
'null',
|
260
|
+
'nUll',
|
261
|
+
'Null',
|
262
|
+
'NULL',
|
263
|
+
'',
|
264
|
+
nil,
|
265
|
+
'2006-09-12',
|
266
|
+
'2006-09-11T17:28:07Z',
|
267
|
+
'2006-09-11T17:28:07+09:00',
|
268
|
+
'2006-09-11 17:28:07.662694 +09:00',
|
269
|
+
'=',
|
270
|
+
].each {|c|
|
271
|
+
['','hoge'].each {|ext|
|
272
|
+
src = (c.class == String) ? (c + ext) : c
|
273
|
+
y = src.ya2yaml(
|
274
|
+
:escape_as_utf8 => true
|
275
|
+
)
|
276
|
+
r = YAML.load(y)
|
277
|
+
assert_equal(
|
278
|
+
src,
|
279
|
+
r,
|
280
|
+
'ambiguous elements should round-trip properly'
|
281
|
+
)
|
282
|
+
}
|
283
|
+
}
|
284
|
+
end
|
285
|
+
|
286
|
+
def test_roundtrip_string
|
287
|
+
chars = "aあ\t\-\?,\[\{\#&\*!\|>'\"\%\@\`.\\ \n\xc2\xa0\xe2\x80\xa8".split('')
|
288
|
+
f = lambda { |r| chars.map { |c| r.map { |rc| c + rc } }.flatten }
|
289
|
+
f[f[f[[""]]]].each do |src|
|
290
|
+
y = src.ya2yaml(
|
291
|
+
:printable_with_syck => true,
|
292
|
+
:escape_b_specific => true,
|
293
|
+
:escape_as_utf8 => true)
|
294
|
+
r = YAML.load(y)
|
295
|
+
assert_equal(src, r, 'string of special characters should round-trip properly')
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# patch by pawel.j.radecki at gmail.com. thanks!
|
300
|
+
def test_roundtrip_symbols
|
301
|
+
symbol1 = :"Batman: The Dark Knight - Why So Serious?!"
|
302
|
+
result_symbol1 = YAML.load(symbol1.ya2yaml)
|
303
|
+
assert_equal(symbol1,result_symbol1)
|
304
|
+
|
305
|
+
symbol2 = :"Batman: The Dark Knight - \"Why So Serious?!\""
|
306
|
+
result_symbol2 = YAML.load(symbol2.ya2yaml)
|
307
|
+
assert_equal(symbol2,result_symbol2)
|
308
|
+
|
309
|
+
# # YAML.load problem: the quotes within the symbol are lost here
|
310
|
+
# symbol3 = :"\"Batman: The Dark Knight - Why So Serious?!\""
|
311
|
+
# result_symbol3 = YAML.load(symbol3.ya2yaml)
|
312
|
+
# assert_equal(symbol3,result_symbol3)
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_roundtrip_types
|
316
|
+
objects = [
|
317
|
+
[],
|
318
|
+
[1],
|
319
|
+
{},
|
320
|
+
{'foo' => 'bar'},
|
321
|
+
nil,
|
322
|
+
'hoge',
|
323
|
+
"abc\nxyz\n",
|
324
|
+
(s = "\xff\xff"),
|
325
|
+
true,
|
326
|
+
false,
|
327
|
+
1000,
|
328
|
+
1000.1,
|
329
|
+
-1000,
|
330
|
+
-1000.1,
|
331
|
+
Date.new(2009,2,9),
|
332
|
+
Time.local(2009,2,9,16,35,22),
|
333
|
+
:foo,
|
334
|
+
1..10,
|
335
|
+
/abc\nxyz/i,
|
336
|
+
@struct,
|
337
|
+
@klass,
|
338
|
+
]
|
339
|
+
s.force_encoding("BINARY") if s.respond_to? :force_encoding
|
340
|
+
|
341
|
+
objects.each {|obj|
|
342
|
+
src = case obj.class.to_s
|
343
|
+
when 'Array'
|
344
|
+
(obj.length) == 0 ? [] : objects
|
345
|
+
when 'Hash'
|
346
|
+
if (obj.length) == 0
|
347
|
+
{}
|
348
|
+
else
|
349
|
+
h = {}
|
350
|
+
c = 0
|
351
|
+
objects.each {|val|
|
352
|
+
h[c] = {}
|
353
|
+
objects.each {|key|
|
354
|
+
h[c][key] = val unless (key.class == Hash || key.class == Moo)
|
355
|
+
}
|
356
|
+
c += 1
|
357
|
+
}
|
358
|
+
h
|
359
|
+
end
|
360
|
+
else
|
361
|
+
obj
|
362
|
+
end
|
363
|
+
y = src.ya2yaml(
|
364
|
+
:syck_compatible => true
|
365
|
+
)
|
366
|
+
|
367
|
+
r = YAML.load(y)
|
368
|
+
assert_equal(
|
369
|
+
src,
|
370
|
+
r,
|
371
|
+
'types other than String should round-trip properly'
|
372
|
+
)
|
373
|
+
}
|
374
|
+
end
|
375
|
+
|
376
|
+
def test_roundtrip_various
|
377
|
+
[
|
378
|
+
[1,2,['c','d',[[['e']],[]],'f'],3,Time.local(2009,2,9,17,9),[[:foo]],nil,true,false,[],{},{[123,223]=>456},{[1]=>2,'a'=>'b','c' => [9,9,9],Time.local(2009,2,9,17,10) => 'hoge'},],
|
379
|
+
[],
|
380
|
+
{[123,223]=>456},
|
381
|
+
{},
|
382
|
+
{'foo' => {1 => {2=>3,4=>5},6 => [7,8]}},
|
383
|
+
"abc",
|
384
|
+
" abc\n def\ndef\ndef\ndef\ndef\n",
|
385
|
+
"abc\n def\ndef\n",
|
386
|
+
"abc\n def\ndef\n\n",
|
387
|
+
"abc\n def\ndef\n\n ",
|
388
|
+
"abc\n def\ndef\n \n",
|
389
|
+
"abc\n def\ndef \n \n",
|
390
|
+
"abc\n def\ndef \n \n ",
|
391
|
+
' ほげほげほげ',
|
392
|
+
{"ほげ\nほげ\n ほげ" => 123},
|
393
|
+
[["ほげ\nほげ\n ほげ"]],
|
394
|
+
"ほげh\x4fge\nほげ\nほげ",
|
395
|
+
[{'ほげ'=>'abc',"ほげ\nほげ"=>'ほげ'},'ほげ',@text],
|
396
|
+
[Date.today,-9.011,0.023,4,-5,{1=>-2,-1=>@text,'_foo'=>'bar','ぬお-ぬお'=>321}],
|
397
|
+
{1=>-2,-1=>@gif,'_foo'=>'bar','ぬお-ぬお'=>321},
|
398
|
+
].each {|src|
|
399
|
+
y = src.ya2yaml(
|
400
|
+
:syck_compatible => true
|
401
|
+
)
|
402
|
+
|
403
|
+
r = YAML.load(y)
|
404
|
+
assert_equal(
|
405
|
+
src,
|
406
|
+
r,
|
407
|
+
'various types should round-trip properly'
|
408
|
+
)
|
409
|
+
}
|
410
|
+
end
|
411
|
+
|
412
|
+
end
|
413
|
+
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kch-ya2yaml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.29.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Akira FUNAI
|
8
|
+
autorequire: ya2yaml
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
date: 2010-01-11 00:00:00 -02:00
|
12
|
+
default_executable:
|
13
|
+
dependencies: []
|
14
|
+
|
15
|
+
description:
|
16
|
+
email: funai.akira@gmail.com
|
17
|
+
executables: []
|
18
|
+
|
19
|
+
extensions: []
|
20
|
+
|
21
|
+
extra_rdoc_files:
|
22
|
+
- README
|
23
|
+
files:
|
24
|
+
- lib/ya2yaml.rb
|
25
|
+
- README
|
26
|
+
- LICENSE
|
27
|
+
- test/t.gif
|
28
|
+
- test/t.yaml
|
29
|
+
- test/test.rb
|
30
|
+
has_rdoc: true
|
31
|
+
homepage: http://rubyforge.org/projects/ya2yaml/
|
32
|
+
licenses: []
|
33
|
+
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- --main
|
37
|
+
- README
|
38
|
+
- --charset
|
39
|
+
- UTF8
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.0.0
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.3.5
|
58
|
+
signing_key:
|
59
|
+
specification_version: 1
|
60
|
+
summary: An UTF8 safe YAML dumper.
|
61
|
+
test_files:
|
62
|
+
- test/test.rb
|