docdiff 0.6.2 → 0.6.5

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +29 -52
  3. data/README.md +352 -0
  4. data/README_ja.md +352 -0
  5. data/Rakefile +2 -42
  6. data/bin/docdiff +1 -185
  7. data/{docdiff.conf.example → doc/example/docdiff.conf.example} +4 -3
  8. data/doc/man/docdiff.adoc +146 -0
  9. data/doc/news.md +189 -0
  10. data/doc/shell_completion/_docdiff.zsh +51 -0
  11. data/doc/shell_completion/docdiff.bash +68 -0
  12. data/docdiff.gemspec +2 -0
  13. data/lib/doc_diff.rb +19 -40
  14. data/lib/docdiff/cli.rb +281 -0
  15. data/lib/docdiff/version.rb +1 -1
  16. data/lib/docdiff/view.rb +22 -10
  17. data/test/charstring_test.rb +121 -121
  18. data/test/cli_test.rb +312 -0
  19. data/test/docdiff_test.rb +0 -23
  20. data/test/document_test.rb +109 -109
  21. data/test/fixture/01_ja_utf8_lf.txt +2 -0
  22. data/test/fixture/02_ja_utf8_lf.txt +2 -0
  23. data/test/fixture/format_wdiff.conf +1 -0
  24. data/test/fixture/simple.conf +9 -0
  25. data/test/view_test.rb +135 -111
  26. metadata +58 -37
  27. data/devutil/changelog.sh +0 -40
  28. data/index.html +0 -181
  29. data/langfilter.rb +0 -10
  30. data/readme.html +0 -750
  31. data/readme.md +0 -185
  32. /data/{docdiffwebui.cgi → doc/example/docdiffwebui.cgi} +0 -0
  33. /data/{docdiffwebui.html → doc/example/docdiffwebui.html} +0 -0
  34. /data/{img/docdiff-screenshot-format-html-digest-firefox.png → doc/img/screenshot-format-html-digest-firefox.png} +0 -0
  35. /data/{img/docdiff-screenshot-format-html-firefox.png → doc/img/screenshot-format-html-firefox.png} +0 -0
  36. /data/{img/docdiff-screenshot-format-tty-cmdexe-en.png → doc/img/screenshot-format-tty-cmdexe-en.png} +0 -0
  37. /data/{img/docdiff-screenshot-format-tty-cmdexe-ja.png → doc/img/screenshot-format-tty-cmdexe-ja.png} +0 -0
  38. /data/{img/docdiff-screenshot-format-tty-rxvtunicode-en.png → doc/img/screenshot-format-tty-rxvtunicode-en.png} +0 -0
  39. /data/{img/docdiff-screenshot-format-tty-rxvtunicode-ja.png → doc/img/screenshot-format-tty-rxvtunicode-ja.png} +0 -0
  40. /data/{img/docdiff-screenshot-format-tty-xterm-en.png → doc/img/screenshot-format-tty-xterm-en.png} +0 -0
  41. /data/{img/docdiff-screenshot-format-tty-xterm-ja.png → doc/img/screenshot-format-tty-xterm-ja.png} +0 -0
  42. /data/{img/docdiff-screenshot-resolution-linewordchar-xterm.png → doc/img/screenshot-resolution-linewordchar-xterm.png} +0 -0
  43. /data/{sample/01.en.ascii.cr → test/fixture/01_en_ascii_cr.txt} +0 -0
  44. /data/{sample/01.en.ascii.crlf → test/fixture/01_en_ascii_crlf.txt} +0 -0
  45. /data/{sample/01.en.ascii.lf → test/fixture/01_en_ascii_lf.txt} +0 -0
  46. /data/{sample/01.ja.eucjp.lf → test/fixture/01_ja_eucjp_lf.txt} +0 -0
  47. /data/{sample/01.ja.sjis.cr → test/fixture/01_ja_sjis_cr.txt} +0 -0
  48. /data/{sample/01.ja.sjis.crlf → test/fixture/01_ja_sjis_crlf.txt} +0 -0
  49. /data/{sample/01.ja.utf8.crlf → test/fixture/01_ja_utf8_crlf.txt} +0 -0
  50. /data/{sample/02.en.ascii.cr → test/fixture/02_en_ascii_cr.txt} +0 -0
  51. /data/{sample/02.en.ascii.crlf → test/fixture/02_en_ascii_crlf.txt} +0 -0
  52. /data/{sample/02.en.ascii.lf → test/fixture/02_en_ascii_lf.txt} +0 -0
  53. /data/{sample/02.ja.eucjp.lf → test/fixture/02_ja_eucjp_lf.txt} +0 -0
  54. /data/{sample/02.ja.sjis.cr → test/fixture/02_ja_sjis_cr.txt} +0 -0
  55. /data/{sample/02.ja.sjis.crlf → test/fixture/02_ja_sjis_crlf.txt} +0 -0
  56. /data/{sample/02.ja.utf8.crlf → test/fixture/02_ja_utf8_crlf.txt} +0 -0
  57. /data/{sample/humpty_dumpty01.ascii.lf → test/fixture/humpty_dumpty01_ascii_lf.txt} +0 -0
  58. /data/{sample/humpty_dumpty02.ascii.lf → test/fixture/humpty_dumpty02_ascii_lf.txt} +0 -0
@@ -0,0 +1,281 @@
1
+ require 'optparse'
2
+
3
+ class DocDiff
4
+ module CLI
5
+ def self.parse_options!(args, base_options: {})
6
+ o = base_options.dup
7
+
8
+ option_parser = OptionParser.new do |parser|
9
+ parser.on(
10
+ '--resolution=RESOLUTION',
11
+ resolutions = ['line', 'word', 'char'],
12
+ 'specify resolution (granularity)',
13
+ "#{resolutions.join('|')} (default: word)"
14
+ ){|s| o[:resolution] = (s || "word")}
15
+ parser.on('--line', 'same as --resolution=line'){o[:resolution] = "line"}
16
+ parser.on('--word', 'same as --resolution=word'){o[:resolution] = "word"}
17
+ parser.on('--char', 'same as --resolution=char'){o[:resolution] = "char"}
18
+
19
+ parser.on(
20
+ '--encoding=ENCODING',
21
+ encodings = ['ASCII', 'EUC-JP', 'Shift_JIS', 'CP932', 'UTF-8', 'auto'],
22
+ "specify character encoding",
23
+ "#{encodings.join('|')} (default: auto)",
24
+ "(try ASCII for single byte encodings such as ISO-8859)"
25
+ ){|s| o[:encoding] = (s || "auto")}
26
+ parser.on('--ascii', 'same as --encoding=ASCII'){o[:encoding] = "ASCII"}
27
+ parser.on('--iso8859', 'same as --encoding=ASCII'){o[:encoding] = "ASCII"}
28
+ parser.on('--iso8859x', 'same as --encoding=ASCII (deprecated)'){o[:encoding] = "ASCII"}
29
+ parser.on('--eucjp', 'same as --encoding=EUC-JP'){o[:encoding] = "EUC-JP"}
30
+ parser.on('--sjis', 'same as --encoding=Shift_JIS'){o[:encoding] = "Shift_JIS"}
31
+ parser.on('--cp932', 'same as --encoding=CP932'){o[:encoding] = "CP932"}
32
+ parser.on('--utf8', 'same as --encoding=UTF-8'){o[:encoding] = "UTF-8"}
33
+
34
+ parser.on(
35
+ '--eol=EOL',
36
+ eols = ['CR','LF','CRLF','auto'],
37
+ 'specify end-of-line character',
38
+ "#{eols.join('|')} (default: auto)",
39
+ ){|s| o[:eol] = (s || "auto")}
40
+ parser.on('--cr', 'same as --eol=CR'){o[:eol] = "CR"}
41
+ parser.on('--lf', 'same as --eol=LF'){o[:eol] = "LF"}
42
+ parser.on('--crlf', 'same as --eol=CRLF'){o[:eol] = "CRLF"}
43
+
44
+ parser.on(
45
+ '--format=FORMAT',
46
+ formats = ['tty', 'manued', 'html', 'wdiff', 'stat', 'user'],
47
+ 'specify output format',
48
+ "#{formats.join('|')} (default: html) (stat is deprecated)",
49
+ '(user tags can be defined in config file)'
50
+ ){|s| o[:format] = (s || "manued")}
51
+ parser.on('--tty', 'same as --format=tty'){o[:format] = "tty"}
52
+ parser.on('--manued', 'same as --format=manued'){o[:format] = "manued"}
53
+ parser.on('--html', 'same as --format=html'){o[:format] = "html"}
54
+ parser.on('--wdiff', 'same as --format=wdiff'){o[:format] = "wdiff"}
55
+ parser.on('--stat', 'same as --format=stat (not implemented) (deprecated)'){o[:format] = "stat"}
56
+
57
+ parser.on(
58
+ '--label LABEL', '-L LABEL',
59
+ 'use label instead of file name (not implemented; exists for compatibility with diff)'
60
+ ){|s| o[:label] ||= []; o[:label] << s}
61
+
62
+ parser.on('--digest', 'digest output, do not show all'){o[:digest] = true}
63
+ parser.on('--summary', 'same as --digest'){o[:digest] = true}
64
+
65
+ parser.on(
66
+ '--display=DISPLAY',
67
+ display_types = ['inline', 'block', 'multi'],
68
+ 'specify presentation type (effective only with digest; experimental feature)',
69
+ "#{display_types.join('|')} (default: inline) (multi is deprecated)",
70
+ ){|s| o[:display] ||= s.downcase}
71
+
72
+ parser.on('--cache', 'use file cache (not implemented) (deprecated)'){o[:cache] = true}
73
+ parser.on(
74
+ '--pager=PAGER', String,
75
+ 'specify pager (if available, $DOCDIFF_PAGER is used by default)'
76
+ ){|s| o[:pager] = s}
77
+ parser.on('--no-pager', 'do not use pager'){o[:pager] = false}
78
+ parser.on('--config-file=FILE', String, 'specify config file to read'){|s| o[:config_file] = s}
79
+ parser.on('--no-config-file', 'do not read config files'){o[:no_config_file] = true}
80
+ parser.on('--verbose', 'run verbosely (not well-supported) (deprecated)'){o[:verbose] = true}
81
+
82
+ parser.on('--help', 'show this message'){puts parser; exit(0)}
83
+ parser.on('--version', 'show version'){puts Docdiff::VERSION; exit(0)}
84
+ parser.on('--license', 'show license (deprecated)'){puts DocDiff::License; exit(0)}
85
+ parser.on('--author', 'show author(s) (deprecated)'){puts DocDiff::Author; exit(0)}
86
+
87
+ parser.on_tail(
88
+ "When invoked as worddiff or chardiff, resolution will be set accordingly.",
89
+ "Config files: /etc/docdiff/docdiff.conf, ~/.config/docdiff/docdiff.conf (or ~/etc/docdiff/docdiff.conf (deprecated))"
90
+ )
91
+ end
92
+
93
+ option_parser.parse!(args)
94
+ o
95
+ end
96
+
97
+ def self.parse_config_file_content(content)
98
+ result = {}
99
+ return result if content.size <= 0
100
+ lines = content.dup.split(/\r\n|\r|\n/).compact
101
+ lines.collect!{|line| line.sub(/#.*$/, '')}
102
+ lines.collect!{|line| line.strip}
103
+ lines.delete_if{|line| line == ""}
104
+ lines.each{|line|
105
+ raise 'line does not include " = ".' unless /[\s]+=[\s]+/.match line
106
+ name_src, value_src = line.split(/[\s]+=[\s]+/)
107
+ raise "Invalid name: #{name_src.inspect}" if (/\s/.match name_src)
108
+ raise "Invalid value: #{value_src.inspect}" unless value_src.kind_of?(String)
109
+ name = name_src.intern
110
+ value = value_src
111
+ value = true if ['on','yes','true'].include? value_src.downcase
112
+ value = false if ['off','no','false'].include? value_src.downcase
113
+ value = value_src.to_i if /^[0-9]+$/.match value_src
114
+ result[name] = value
115
+ }
116
+ result
117
+ end
118
+
119
+ def self.read_config_from_file(filename)
120
+ content = nil
121
+ begin
122
+ File.open(filename, "r"){|f| content = f.read}
123
+ rescue => exception
124
+ raise exception
125
+ ensure
126
+ message =
127
+ case exception
128
+ in Errno::ENOENT
129
+ "config file not found: #{filename.inspect}"
130
+ in Errno::EACCES
131
+ "permission denied for reading: #{filename.inspect}"
132
+ else
133
+ "something unexpected happened: #{filename.inspect}"
134
+ end
135
+ if content
136
+ config = parse_config_file_content(content)
137
+ else
138
+ message = "config file empty: #{filename.inspect}"
139
+ end
140
+ end
141
+ [config, message]
142
+ end
143
+
144
+ def self.print_or_write_to_pager(content, pager)
145
+ if STDOUT.tty? && pager.is_a?(String) && !pager.empty?
146
+ IO.popen(pager, "w"){|f| f.print content}
147
+ else
148
+ print content
149
+ end
150
+ end
151
+
152
+ def self.run
153
+ command_line_config = parse_options!(ARGV)
154
+
155
+ system_config =
156
+ unless command_line_config[:no_config_file]
157
+ possible_system_config_file_names = [
158
+ DocDiff::SystemConfigFileName,
159
+ ]
160
+ existing_system_config_file_names =
161
+ possible_system_config_file_names.select{|fn| File.exist? fn}
162
+ if existing_system_config_file_names.size >= 2
163
+ raise <<~EOS
164
+ More than one system config file found, using the first one: \
165
+ #{existing_system_config_file_names.inspect}
166
+ EOS
167
+ end
168
+ filename = existing_system_config_file_names.first
169
+ config, message = read_config_from_file(filename)
170
+ STDERR.print message if command_line_config[:verbose]
171
+ config
172
+ end
173
+
174
+ user_config =
175
+ unless command_line_config[:no_config_file]
176
+ possible_user_config_file_names = [
177
+ DocDiff::UserConfigFileName,
178
+ DocDiff::AltUserConfigFileName,
179
+ DocDiff::XDGUserConfigFileName,
180
+ ]
181
+ existing_user_config_file_names =
182
+ possible_user_config_file_names.select{|fn| File.exist? fn}
183
+ if existing_user_config_file_names.size >= 2
184
+ raise <<~EOS
185
+ Only one user config file can be used at the same time. \
186
+ Keep one and remove or rename the others: \
187
+ #{existing_user_config_file_names.inspect}
188
+ EOS
189
+ end
190
+ filename = existing_user_config_file_names.first
191
+ config, message = read_config_from_file(filename)
192
+ STDERR.print message if command_line_config[:verbose]
193
+ config
194
+ end
195
+
196
+ config_from_specified_file =
197
+ if filename = command_line_config[:config_file]
198
+ config, message = read_config_from_file(filename)
199
+ STDERR.print message if command_line_config[:verbose] == true
200
+ config
201
+ end
202
+
203
+ config_from_program_name =
204
+ case File.basename($PROGRAM_NAME, ".*")
205
+ when "worddiff" then {:resolution => "word"}
206
+ when "chardiff" then {:resolution => "char"}
207
+ end
208
+
209
+ config_from_env_vars = {}
210
+ if (pager = ENV['DOCDIFF_PAGER']) && !pager.empty?
211
+ config_from_env_vars[:pager] = pager
212
+ end
213
+
214
+ config_in_effect = DocDiff::DEFAULT_CONFIG.dup
215
+ config_in_effect.merge!(config_from_program_name) if config_from_program_name
216
+ config_in_effect.merge!(system_config) if system_config
217
+ config_in_effect.merge!(user_config) if user_config
218
+ config_in_effect.merge!(config_from_env_vars) if config_from_env_vars
219
+ config_in_effect.merge!(config_from_specified_file) if config_from_specified_file
220
+ config_in_effect.merge!(command_line_config) if command_line_config
221
+
222
+ docdiff = DocDiff.new(config: config_in_effect)
223
+
224
+ file1_content = nil
225
+ file2_content = nil
226
+ raise "Try `#{File.basename($0)} --help' for more information." if ARGV[0].nil?
227
+ raise "Specify at least 2 target files." unless ARGV[0] && ARGV[1]
228
+ ARGV[0] = "/dev/stdin" if ARGV[0] == "-"
229
+ ARGV[1] = "/dev/stdin" if ARGV[1] == "-"
230
+ raise "No such file: #{ARGV[0]}." unless FileTest.exist?(ARGV[0])
231
+ raise "No such file: #{ARGV[1]}." unless FileTest.exist?(ARGV[1])
232
+ raise "#{ARGV[0]} is not readable." unless FileTest.readable?(ARGV[0])
233
+ raise "#{ARGV[1]} is not readable." unless FileTest.readable?(ARGV[1])
234
+ File.open(ARGV[0], "r"){|f| file1_content = f.read}
235
+ File.open(ARGV[1], "r"){|f| file2_content = f.read}
236
+
237
+ doc1 = nil
238
+ doc2 = nil
239
+
240
+ encoding1 = docdiff.config[:encoding]
241
+ encoding2 = docdiff.config[:encoding]
242
+ eol1 = docdiff.config[:eol]
243
+ eol2 = docdiff.config[:eol]
244
+
245
+ if docdiff.config[:encoding] == "auto"
246
+ encoding1 = DocDiff::CharString.guess_encoding(file1_content)
247
+ encoding2 = DocDiff::CharString.guess_encoding(file2_content)
248
+ case
249
+ when (encoding1 == "UNKNOWN" or encoding2 == "UNKNOWN")
250
+ raise "Document encoding unknown (#{encoding1}, #{encoding2})."
251
+ when encoding1 != encoding2
252
+ raise "Document encoding mismatch (#{encoding1}, #{encoding2})."
253
+ end
254
+ end
255
+
256
+ if docdiff.config[:eol] == "auto"
257
+ eol1 = DocDiff::CharString.guess_eol(file1_content)
258
+ eol2 = DocDiff::CharString.guess_eol(file2_content)
259
+ case
260
+ when (eol1.nil? or eol2.nil?)
261
+ raise "Document eol is nil (#{eol1.inspect}, #{eol2.inspect}). The document might be empty."
262
+ when (eol1 == 'UNKNOWN' or eol2 == 'UNKNOWN')
263
+ raise "Document eol unknown (#{eol1.inspect}, #{eol2.inspect})."
264
+ when (eol1 != eol2)
265
+ raise "Document eol mismatch (#{eol1}, #{eol2})."
266
+ end
267
+ end
268
+
269
+ doc1 = DocDiff::Document.new(file1_content, encoding1, eol1)
270
+ doc2 = DocDiff::Document.new(file2_content, encoding2, eol2)
271
+
272
+ output = docdiff.run(doc1, doc2,
273
+ {:resolution => docdiff.config[:resolution],
274
+ :format => docdiff.config[:format],
275
+ :digest => docdiff.config[:digest],
276
+ :display => docdiff.config[:display]})
277
+
278
+ print_or_write_to_pager(output, docdiff.config[:pager])
279
+ end
280
+ end
281
+ end
@@ -1,3 +1,3 @@
1
1
  module Docdiff
2
- VERSION = "0.6.2"
2
+ VERSION = "0.6.5"
3
3
  end
data/lib/docdiff/view.rb CHANGED
@@ -47,10 +47,22 @@ class View
47
47
  end
48
48
 
49
49
  def escape_inside(str, tags)
50
- str.gsub(tags[:inside_escape_pat]){|m| tags[:inside_escape_dic][m]}
50
+ str.gsub(tags[:inside_escape_pat]){|m|
51
+ if replacement = tags[:inside_escape_dic][m]
52
+ replacement
53
+ else
54
+ m
55
+ end
56
+ }
51
57
  end
52
58
  def escape_outside(str, tags)
53
- str.gsub(tags[:outside_escape_pat]){|m| tags[:outside_escape_dic][m]}
59
+ str.gsub(tags[:outside_escape_pat]){|m|
60
+ if replacement = tags[:outside_escape_dic][m]
61
+ replacement
62
+ else
63
+ m
64
+ end
65
+ }
54
66
  end
55
67
 
56
68
  def apply_style(tags, headfoot = true)
@@ -152,7 +164,7 @@ class View
152
164
  case display
153
165
  when 'inline'
154
166
  result << (e_head.call(pos_str) + e_cxt_pre + e_chg + e_cxt_post + e_foot)
155
- when 'multi'
167
+ when /block|multi/
156
168
  result << (e_head.call(pos_str) + e_cxt_pre + e_chgdel + e_cxt_post +
157
169
  e_cxt_pre + e_chgadd + e_cxt_post + e_foot)
158
170
  else raise "Unsupported display type: #{display}"
@@ -163,7 +175,7 @@ class View
163
175
  case display
164
176
  when 'inline'
165
177
  result << (e_head.call(pos_str) + e_cxt_pre + e_del + e_cxt_post + e_foot)
166
- when 'multi'
178
+ when /block|multi/
167
179
  result << (e_head.call(pos_str) + e_cxt_pre + e_src + e_cxt_post +
168
180
  e_cxt_pre + e_del + e_cxt_post + e_foot)
169
181
  else raise "Unsupported display type: #{display}"
@@ -174,7 +186,7 @@ class View
174
186
  case display
175
187
  when 'inline'
176
188
  result << (e_head.call(pos_str) + e_cxt_pre + e_add + e_cxt_post + e_foot)
177
- when 'multi'
189
+ when /block|multi/
178
190
  result << (e_head.call(pos_str) + e_cxt_pre + e_src + e_cxt_post +
179
191
  e_cxt_pre + e_add + e_cxt_post + e_foot)
180
192
  else raise "Unsupported display type: #{display}"
@@ -212,7 +224,7 @@ class View
212
224
  []
213
225
  end
214
226
  TTYEscapeDic = {'ThisRandomString' => 'ThisRandomString'}
215
- TTYEscapePat = /(\r\n|#{TTYEscapeDic.keys.collect{|k|Regexp.quote(k)}.join('|')})/m
227
+ TTYEscapePat = /(#{TTYEscapeDic.keys.collect{|k|Regexp.quote(k)}.join('|')})/m
216
228
  def tty_tags()
217
229
  {:outside_escape_dic => TTYEscapeDic,
218
230
  :outside_escape_pat => TTYEscapePat,
@@ -277,7 +289,7 @@ class View
277
289
  end
278
290
  HTMLEscapeDic = {'<'=>'&lt;', '>'=>'&gt;', '&'=>'&amp;', ' '=>'&nbsp;&nbsp;',
279
291
  "\r\n" => "<br />\r\n", "\r" => "<br />\r", "\n" => "<br />\n"}
280
- HTMLEscapePat = /(\r\n|#{HTMLEscapeDic.keys.collect{|k|Regexp.quote(k)}.join('|')})/m
292
+ HTMLEscapePat = /(#{HTMLEscapeDic.keys.collect{|k|Regexp.quote(k)}.join('|')})/m
281
293
  def html_tags()
282
294
  {:outside_escape_dic => HTMLEscapeDic,
283
295
  :outside_escape_pat => HTMLEscapePat,
@@ -372,7 +384,7 @@ class View
372
384
  tags = manued_tags()
373
385
  # manued specific kludge: change should be [a/b] in inline, [a/][/b] in multi
374
386
  display = (overriding_opts and overriding_opts[:display]) || 'inline'
375
- if display == 'multi'
387
+ if /block|multi/.match display
376
388
  tags.update({:end_before_change => '/]', :start_after_change => '[/'})
377
389
  end
378
390
  tags.update(overriding_opts) if overriding_opts
@@ -387,7 +399,7 @@ class View
387
399
  []
388
400
  end
389
401
  WDIFFEscapeDic = {'ThisRandomString' => 'ThisRandomString'}
390
- WDIFFEscapePat = /(\r\n|#{WDIFFEscapeDic.keys.collect{|k|Regexp.quote(k)}.join('|')})/m
402
+ WDIFFEscapePat = /(#{WDIFFEscapeDic.keys.collect{|k|Regexp.quote(k)}.join('|')})/m
391
403
  def wdiff_tags()
392
404
  {:outside_escape_dic => WDIFFEscapeDic,
393
405
  :outside_escape_pat => WDIFFEscapePat,
@@ -431,7 +443,7 @@ class View
431
443
  def user_header(); []; end
432
444
  def user_footer(); []; end
433
445
  UserEscapeDic = {'ThisRandomString' => 'ThisRandomString'}
434
- UserEscapePat = /(\r\n|#{UserEscapeDic.keys.collect{|k|Regexp.quote(k)}.join('|')})/m
446
+ UserEscapePat = /(#{UserEscapeDic.keys.collect{|k|Regexp.quote(k)}.join('|')})/m
435
447
  def user_tags()
436
448
  {:outside_escape_dic => UserEscapeDic,
437
449
  :outside_escape_pat => UserEscapePat,