* io.c (rb_open_file): encoding in mode string was ignored if perm is
[ruby-svn.git] / lib / yaml / baseemitter.rb
bloba1992f498b419ab9f95d649100b702cbd8f597a8
2 # BaseEmitter
5 require 'yaml/constants'
6 require 'yaml/encoding'
7 require 'yaml/error'
9 module YAML
11     module BaseEmitter
13         def options( opt = nil )
14             if opt
15                 @options[opt] || YAML::DEFAULTS[opt]
16             else
17                 @options
18             end
19         end
21         def options=( opt )
22             @options = opt
23         end
25         #
26         # Emit binary data
27         #
28         def binary_base64( value )
29             self << "!binary "
30             self.node_text( [value].pack("m"), '|' )
31         end
33                 #
34                 # Emit plain, normal flowing text
35                 #
36                 def node_text( value, block = nil )
37             @seq_map = false
38                         valx = value.dup
39             unless block
40             block =
41                 if options(:UseBlock)
42                     '|'
43                 elsif not options(:UseFold) and valx =~ /\n[ \t]/ and not valx =~ /#{YAML::ESCAPE_CHAR}/
44                     '|'
45                 else
46                     '>'
47                 end 
49                 indt = $&.to_i if block =~ /\d+/
50                 if valx =~ /(\A\n*[ \t#]|^---\s+)/
51                     indt = options(:Indent) unless indt.to_i > 0
52                     block += indt.to_s
53                 end
55             block +=
56                 if valx =~ /\n\Z\n/
57                     "+"
58                 elsif valx =~ /\Z\n/
59                     ""
60                 else
61                     "-"
62                 end
63             end
64             block += "\n"
65             if block[0] == ?"
66                 esc_skip = ( "\t\n" unless valx =~ /^[ \t]/ ) || ""
67                 valx = fold( YAML::escape( valx, esc_skip ) + "\"" ).chomp
68                 self << '"' + indent_text( valx, indt, false )
69             else
70                 if block[0] == ?> 
71                     valx = fold( valx ) 
72                 end
73                 #p [block, indt]
74                 self << block + indent_text( valx, indt )
75             end
76                 end
78                 #
79                 # Emit a simple, unqouted string
80                 #
81                 def simple( value )
82             @seq_map = false
83             self << value.to_s
84                 end
86                 #
87                 # Emit double-quoted string
88                 #
89                 def double( value )
90                         "\"#{YAML.escape( value )}\"" 
91                 end
93                 #
94                 # Emit single-quoted string
95                 #
96                 def single( value )
97                         "'#{value}'"
98                 end
100                 #
101                 # Write a text block with the current indent
102                 #
103                 def indent_text( text, mod, first_line = true )
104                         return "" if text.to_s.empty?
105             spacing = indent( mod )
106             text = text.gsub( /\A([^\n])/, "#{ spacing }\\1" ) if first_line
107                         return text.gsub( /\n^([^\n])/, "\n#{spacing}\\1" )
108                 end
110                 #
111                 # Write a current indent
112                 #
113         def indent( mod = nil )
114             #p [ self.id, level, mod, :INDENT ]
115             if level <= 0
116                 mod ||= 0
117             else
118                 mod ||= options(:Indent)
119                 mod += ( level - 1 ) * options(:Indent)
120             end
121             return " " * mod
122                 end
124                 #
125                 # Add indent to the buffer
126                 #
127                 def indent!
128                         self << indent
129                 end
131                 #
132                 # Folding paragraphs within a column
133                 #
134                 def fold( value )
135             value.gsub( /(^[ \t]+.*$)|(\S.{0,#{options(:BestWidth) - 1}})(?:[ \t]+|(\n+(?=[ \t]|\Z))|$)/ ) do
136                 $1 || $2 + ( $3 || "\n" )
137             end
138                 end
140         #
141         # Quick mapping
142         #
143         def map( type, &e )
144             val = Mapping.new
145             e.call( val )
146                         self << "#{type} " if type.length.nonzero?
148                         #
149                         # Empty hashes
150                         #
151                         if val.length.zero?
152                                 self << "{}"
153                 @seq_map = false
154                         else
155                 # FIXME
156                 # if @buffer.length == 1 and options(:UseHeader) == false and type.length.zero? 
157                             #     @headless = 1 
158                 # end
160                 defkey = @options.delete( :DefaultKey )
161                 if defkey
162                     seq_map_shortcut
163                     self << "= : "
164                     defkey.to_yaml( :Emitter => self )
165                 end
167                                 #
168                                 # Emit the key and value
169                                 #
170                 val.each { |v|
171                     seq_map_shortcut
172                     if v[0].is_complex_yaml?
173                         self << "? "
174                     end
175                     v[0].to_yaml( :Emitter => self )
176                     if v[0].is_complex_yaml?
177                         self << "\n"
178                         indent!
179                     end
180                     self << ": " 
181                     v[1].to_yaml( :Emitter => self )
182                 }
183                         end
184         end
186         def seq_map_shortcut
187             # FIXME: seq_map needs to work with the new anchoring system
188             # if @seq_map
189             #     @anchor_extras[@buffer.length - 1] = "\n" + indent
190             #     @seq_map = false
191             # else
192                 self << "\n"
193                 indent! 
194             # end
195         end
197         #
198         # Quick sequence
199         #
200         def seq( type, &e )
201             @seq_map = false
202             val = Sequence.new
203             e.call( val )
204                         self << "#{type} " if type.length.nonzero?
206                         #
207                         # Empty arrays
208                         #
209                         if val.length.zero?
210                                 self << "[]"
211                         else
212                 # FIXME
213                 # if @buffer.length == 1 and options(:UseHeader) == false and type.length.zero? 
214                             #     @headless = 1 
215                 # end
217                                 #
218                                 # Emit the key and value
219                                 #
220                 val.each { |v|
221                     self << "\n"
222                     indent!
223                     self << "- "
224                     @seq_map = true if v.class == Hash
225                     v.to_yaml( :Emitter => self )
226                 }
227                         end
228         end
230     end
232     #
233     # Emitter helper classes
234     #
235     class Mapping < Array
236         def add( k, v )
237             push [k, v]
238         end
239     end
241     class Sequence < Array
242         def add( v )
243             push v
244         end
245     end