docdiff 0.6.4 → 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.
- checksums.yaml +4 -4
- data/Makefile +6 -7
- data/README.md +1 -0
- data/README_ja.md +1 -0
- data/bin/docdiff +1 -208
- data/doc/news.md +9 -0
- data/docdiff.gemspec +1 -0
- data/lib/doc_diff.rb +13 -47
- data/lib/docdiff/cli.rb +281 -0
- data/lib/docdiff/version.rb +1 -1
- data/lib/docdiff/view.rb +18 -6
- data/test/cli_test.rb +312 -0
- data/test/docdiff_test.rb +0 -23
- data/test/fixture/format_wdiff.conf +1 -0
- data/test/fixture/simple.conf +9 -0
- metadata +21 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6c9449dff2946f72c9da89b1a9cd443ea999368160f1712e6da7c56dc2a094f
|
|
4
|
+
data.tar.gz: 67b4fbff27a464b6605e1a7b255c5de811a1fbdba86c9049f8b4adce8a7159f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ee7f1e478639c73fe75bfe642e37912cbbb9df3bf7a13c287c932440e6c3fed68c99340c06e1411302f0afcadaefbabf442e1b229457d9615fe77a367f62682
|
|
7
|
+
data.tar.gz: f62327185b55508a7a1045e1f479c1e1a79998c005aeede62f759743bd013f6e1b11bbd69343083c38f7587cf29aa069a3a9808b49c78eaae8d71756a4e7f747
|
data/Makefile
CHANGED
|
@@ -14,12 +14,12 @@ DESTDIR =
|
|
|
14
14
|
PREFIX = /usr/local
|
|
15
15
|
datadir = $(DESTDIR)$(PREFIX)/share
|
|
16
16
|
|
|
17
|
-
all:
|
|
17
|
+
all: test
|
|
18
18
|
|
|
19
19
|
test: $(TESTS)
|
|
20
20
|
$(RUBY) -I./lib -e 'ARGV.map{|a| require_relative "#{a}"}' $^
|
|
21
21
|
|
|
22
|
-
docs:
|
|
22
|
+
docs: $(DOCS)
|
|
23
23
|
|
|
24
24
|
%.html: %.md
|
|
25
25
|
$(MD2HTML) --html-title="$(shell grep '^# .*' $< | head -n 1 | sed 's/^# //')" $< \
|
|
@@ -65,8 +65,9 @@ uninstall:
|
|
|
65
65
|
-rm -fr $(DESTDIR)/etc/$(PRODUCT)
|
|
66
66
|
-rm -fr $(datadir)/doc/$(PRODUCT)
|
|
67
67
|
|
|
68
|
-
dist:
|
|
69
|
-
|
|
68
|
+
dist: $(PRODUCT)-$(VERSION).tar.gz
|
|
69
|
+
$(PRODUCT)-$(VERSION).tar.gz:
|
|
70
|
+
git archive --prefix="$(PRODUCT)-$(VERSION)/" --format=tar HEAD --output="$@"
|
|
70
71
|
|
|
71
72
|
gem: $(PRODUCT)-$(VERSION).gem
|
|
72
73
|
$(PRODUCT)-$(VERSION).gem: $(PRODUCT).gemspec
|
|
@@ -74,9 +75,7 @@ $(PRODUCT)-$(VERSION).gem: $(PRODUCT).gemspec
|
|
|
74
75
|
|
|
75
76
|
clean:
|
|
76
77
|
-rm -fr $(DOCS)
|
|
77
|
-
|
|
78
|
-
distclean: clean
|
|
79
78
|
-rm -fr $(PRODUCT)-$(VERSION).tar.gz
|
|
80
79
|
-rm -fr $(PRODUCT)-$(VERSION).gem
|
|
81
80
|
|
|
82
|
-
.PHONY:
|
|
81
|
+
.PHONY: all test docs install uninstall dist gem clean
|
data/README.md
CHANGED
|
@@ -85,6 +85,7 @@ $
|
|
|
85
85
|
* Runtime requirements:
|
|
86
86
|
- [Ruby](https://www.ruby-lang.org/) (>= 3.0)
|
|
87
87
|
* Development requirements:
|
|
88
|
+
- [Test::Unit](https://test-unit.github.io/)
|
|
88
89
|
- Make ([GNU Make](https://www.gnu.org/software/make/))
|
|
89
90
|
- [Git](https://git-scm.com/)
|
|
90
91
|
- [md2html](https://github.com/mity/md4c) (for generating documents)
|
data/README_ja.md
CHANGED
|
@@ -85,6 +85,7 @@ $
|
|
|
85
85
|
* 実行時に必要なソフトウェア:
|
|
86
86
|
- [Ruby](https://www.ruby-lang.org/) (>= 3.0)
|
|
87
87
|
* 開発時に必要なソフトウェア:
|
|
88
|
+
- [Test::Unit](https://test-unit.github.io/)
|
|
88
89
|
- Make ([GNU Make](https://www.gnu.org/software/make/))
|
|
89
90
|
- [Git](https://git-scm.com/)
|
|
90
91
|
- [md2html](https://github.com/mity/md4c)(ドキュメント生成用)
|
data/bin/docdiff
CHANGED
|
@@ -3,212 +3,5 @@
|
|
|
3
3
|
# Copyright (C) 2002-2011 Hisashi MORITA
|
|
4
4
|
# Requirements: Ruby (>= 2.0)
|
|
5
5
|
require 'docdiff'
|
|
6
|
-
require 'optparse'
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
default_config = {
|
|
11
|
-
:resolution => "word",
|
|
12
|
-
:encoding => "auto",
|
|
13
|
-
:eol => "auto",
|
|
14
|
-
:format => "html",
|
|
15
|
-
:cache => true,
|
|
16
|
-
:digest => false,
|
|
17
|
-
:pager => nil,
|
|
18
|
-
:verbose => false
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
clo = command_line_options = {}
|
|
22
|
-
|
|
23
|
-
# if invoked as "worddiff" or "chardiff",
|
|
24
|
-
# appropriate resolution is set respectively.
|
|
25
|
-
case File.basename($0, ".*")
|
|
26
|
-
when "worddiff" then; clo[:resolution] = "word"
|
|
27
|
-
when "chardiff" then; clo[:resolution] = "char"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
ARGV.options {|o|
|
|
31
|
-
o.def_option('--resolution=RESOLUTION',
|
|
32
|
-
resolutions = ['line', 'word', 'char'],
|
|
33
|
-
'specify resolution (granularity)',
|
|
34
|
-
"#{resolutions.join('|')} (default: word)"
|
|
35
|
-
){|s| clo[:resolution] = (s || "word")}
|
|
36
|
-
o.def_option('--line', 'same as --resolution=line'){clo[:resolution] = "line"}
|
|
37
|
-
o.def_option('--word', 'same as --resolution=word'){clo[:resolution] = "word"}
|
|
38
|
-
o.def_option('--char', 'same as --resolution=char'){clo[:resolution] = "char"}
|
|
39
|
-
|
|
40
|
-
o.def_option('--encoding=ENCODING',
|
|
41
|
-
encodings = ['ASCII', 'EUC-JP', 'Shift_JIS', 'CP932', 'UTF-8', 'auto'],
|
|
42
|
-
"specify character encoding",
|
|
43
|
-
"#{encodings.join('|')} (default: auto)",
|
|
44
|
-
"(try ASCII for single byte encodings such as ISO-8859)"
|
|
45
|
-
){|s| clo[:encoding] = (s || "auto")}
|
|
46
|
-
o.def_option('--ascii', 'same as --encoding=ASCII'){clo[:encoding] = "ASCII"}
|
|
47
|
-
o.def_option('--iso8859', 'same as --encoding=ASCII'){clo[:encoding] = "ASCII"}
|
|
48
|
-
o.def_option('--iso8859x', 'same as --encoding=ASCII (deprecated)'){clo[:encoding] = "ASCII"}
|
|
49
|
-
o.def_option('--eucjp', 'same as --encoding=EUC-JP'){clo[:encoding] = "EUC-JP"}
|
|
50
|
-
o.def_option('--sjis', 'same as --encoding=Shift_JIS'){clo[:encoding] = "Shift_JIS"}
|
|
51
|
-
o.def_option('--cp932', 'same as --encoding=CP932'){clo[:encoding] = "CP932"}
|
|
52
|
-
o.def_option('--utf8', 'same as --encoding=UTF-8'){clo[:encoding] = "UTF-8"}
|
|
53
|
-
|
|
54
|
-
o.def_option('--eol=EOL',
|
|
55
|
-
eols = ['CR','LF','CRLF','auto'],
|
|
56
|
-
'specify end-of-line character',
|
|
57
|
-
"#{eols.join('|')} (default: auto)",
|
|
58
|
-
){|s| clo[:eol] = (s || "auto")}
|
|
59
|
-
o.def_option('--cr', 'same as --eol=CR'){clo[:eol] = "CR"}
|
|
60
|
-
o.def_option('--lf', 'same as --eol=LF'){clo[:eol] = "LF"}
|
|
61
|
-
o.def_option('--crlf', 'same as --eol=CRLF'){clo[:eol] = "CRLF"}
|
|
62
|
-
|
|
63
|
-
o.def_option('--format=FORMAT',
|
|
64
|
-
formats = ['tty', 'manued', 'html', 'wdiff', 'stat', 'user'],
|
|
65
|
-
'specify output format',
|
|
66
|
-
"#{formats.join('|')} (default: html) (stat is deprecated)",
|
|
67
|
-
'(user tags can be defined in config file)'
|
|
68
|
-
){|s| clo[:format] = (s || "manued")}
|
|
69
|
-
o.def_option('--tty', 'same as --format=tty'){clo[:format] = "tty"}
|
|
70
|
-
o.def_option('--manued', 'same as --format=manued'){clo[:format] = "manued"}
|
|
71
|
-
o.def_option('--html', 'same as --format=html'){clo[:format] = "html"}
|
|
72
|
-
o.def_option('--wdiff', 'same as --format=wdiff'){clo[:format] = "wdiff"}
|
|
73
|
-
o.def_option('--stat', 'same as --format=stat (not implemented) (deprecated)'){clo[:format] = "stat"}
|
|
74
|
-
|
|
75
|
-
o.def_option('--label LABEL', '-L LABEL',
|
|
76
|
-
'use label instead of file name (not implemented; exists for compatibility with diff)'
|
|
77
|
-
){|s1, s2| clo[:label1], clo[:label2] = s1, s2}
|
|
78
|
-
|
|
79
|
-
o.def_option('--digest', 'digest output, do not show all'){clo[:digest] = true}
|
|
80
|
-
o.def_option('--summary', 'same as --digest'){clo[:digest] = true}
|
|
81
|
-
o.def_option('--display=DISPLAY',
|
|
82
|
-
display_types = ['inline', 'block', 'multi'],
|
|
83
|
-
'specify presentation type (effective only with digest; experimental feature)',
|
|
84
|
-
"#{display_types.join('|')} (default: inline) (multi is deprecated)",
|
|
85
|
-
){|s| clo[:display] ||= s.downcase}
|
|
86
|
-
o.def_option('--cache', 'use file cache (not implemented) (deprecated)'){clo[:cache] = true}
|
|
87
|
-
o.def_option('--pager=PAGER', String,
|
|
88
|
-
'specify pager (if available, $DOCDIFF_PAGER is used by default)'
|
|
89
|
-
){|s| clo[:pager] = s}
|
|
90
|
-
o.def_option('--no-pager', 'do not use pager'){clo[:pager] = false}
|
|
91
|
-
o.def_option('--config-file=FILE', String,
|
|
92
|
-
'specify config file to read'){|s| clo[:config_file] = s}
|
|
93
|
-
o.def_option('--no-config-file',
|
|
94
|
-
'do not read config files'){clo[:no_config_file] = true}
|
|
95
|
-
o.def_option('--verbose', 'run verbosely (not well-supported) (deprecated)'){clo[:verbose] = true}
|
|
96
|
-
|
|
97
|
-
o.def_option('--help', 'show this message'){puts o; exit(0)}
|
|
98
|
-
o.def_option('--version', 'show version'){puts DocDiff::AppVersion; exit(0)}
|
|
99
|
-
o.def_option('--license', 'show license (deprecated)'){puts DocDiff::License; exit(0)}
|
|
100
|
-
o.def_option('--author', 'show author(s) (deprecated)'){puts DocDiff::Author; exit(0)}
|
|
101
|
-
|
|
102
|
-
o.on_tail("When invoked as worddiff or chardiff, resolution will be set accordingly.",
|
|
103
|
-
"Config files: /etc/docdiff/docdiff.conf, ~/.config/docdiff/docdiff.conf (or ~/etc/docdiff/docdiff.conf (deprecated))")
|
|
104
|
-
|
|
105
|
-
o.parse!
|
|
106
|
-
} or exit(1)
|
|
107
|
-
|
|
108
|
-
docdiff = DocDiff.new()
|
|
109
|
-
docdiff.config.update(default_config)
|
|
110
|
-
unless clo[:no_config_file] == true # process_commandline_option
|
|
111
|
-
message = docdiff.process_config_file(DocDiff::SystemConfigFileName)
|
|
112
|
-
if clo[:verbose] == true || docdiff.config[:verbose] == true
|
|
113
|
-
STDERR.print message
|
|
114
|
-
end
|
|
115
|
-
# message = docdiff.process_config_file(DocDiff::UserConfigFileName)
|
|
116
|
-
case
|
|
117
|
-
when [File.exist?(DocDiff::UserConfigFileName),
|
|
118
|
-
File.exist?(DocDiff::AltUserConfigFileName),
|
|
119
|
-
File.exist?(DocDiff::XDGUserConfigFileName)].count(true) >= 2
|
|
120
|
-
raise <<~EOS
|
|
121
|
-
#{DocDiff::UserConfigFileName}, #{DocDiff::AltUserConfigFileName}, and \
|
|
122
|
-
#{DocDiff::XDGUserConfigFileName} cannot be used at the same time. \
|
|
123
|
-
Keep one and remove or rename the others.
|
|
124
|
-
EOS
|
|
125
|
-
when File.exist?(DocDiff::UserConfigFileName)
|
|
126
|
-
message = docdiff.process_config_file(DocDiff::UserConfigFileName)
|
|
127
|
-
when File.exist?(DocDiff::AltUserConfigFileName)
|
|
128
|
-
message = docdiff.process_config_file(DocDiff::AltUserConfigFileName)
|
|
129
|
-
when File.exist?(DocDiff::XDGUserConfigFileName)
|
|
130
|
-
message = docdiff.process_config_file(DocDiff::XDGUserConfigFileName)
|
|
131
|
-
end
|
|
132
|
-
if clo[:verbose] == true || docdiff.config[:verbose] == true
|
|
133
|
-
STDERR.print message
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
unless clo[:config_file].nil?
|
|
137
|
-
if File.exist?(clo[:config_file])
|
|
138
|
-
message = docdiff.process_config_file(clo[:config_file])
|
|
139
|
-
else
|
|
140
|
-
raise "#{clo[:config_file]} does not exist."
|
|
141
|
-
end
|
|
142
|
-
if clo[:verbose] == true || docdiff.config[:verbose] == true
|
|
143
|
-
STDERR.print message
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
docdiff.config.update(clo)
|
|
147
|
-
|
|
148
|
-
docdiff.config[:pager] =
|
|
149
|
-
if (pager = docdiff.config[:pager]).is_a?(String) && !pager.empty?
|
|
150
|
-
pager
|
|
151
|
-
elsif (pager = docdiff.config[:pager]) == false
|
|
152
|
-
pager
|
|
153
|
-
elsif (pager = ENV['DOCDIFF_PAGER']) && !pager.empty?
|
|
154
|
-
pager
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# config stuff done
|
|
158
|
-
|
|
159
|
-
# process the documents
|
|
160
|
-
|
|
161
|
-
file1_content = nil
|
|
162
|
-
file2_content = nil
|
|
163
|
-
raise "Try `#{File.basename($0)} --help' for more information." if ARGV[0].nil?
|
|
164
|
-
raise "Specify at least 2 target files." unless ARGV[0] && ARGV[1]
|
|
165
|
-
ARGV[0] = "/dev/stdin" if ARGV[0] == "-"
|
|
166
|
-
ARGV[1] = "/dev/stdin" if ARGV[1] == "-"
|
|
167
|
-
raise "No such file: #{ARGV[0]}." unless FileTest.exist?(ARGV[0])
|
|
168
|
-
raise "No such file: #{ARGV[1]}." unless FileTest.exist?(ARGV[1])
|
|
169
|
-
raise "#{ARGV[0]} is not readable." unless FileTest.readable?(ARGV[0])
|
|
170
|
-
raise "#{ARGV[1]} is not readable." unless FileTest.readable?(ARGV[1])
|
|
171
|
-
File.open(ARGV[0], "r"){|f| file1_content = f.read}
|
|
172
|
-
File.open(ARGV[1], "r"){|f| file2_content = f.read}
|
|
173
|
-
|
|
174
|
-
doc1 = nil
|
|
175
|
-
doc2 = nil
|
|
176
|
-
|
|
177
|
-
encoding1 = docdiff.config[:encoding]
|
|
178
|
-
encoding2 = docdiff.config[:encoding]
|
|
179
|
-
eol1 = docdiff.config[:eol]
|
|
180
|
-
eol2 = docdiff.config[:eol]
|
|
181
|
-
|
|
182
|
-
if docdiff.config[:encoding] == "auto"
|
|
183
|
-
encoding1 = DocDiff::CharString.guess_encoding(file1_content)
|
|
184
|
-
encoding2 = DocDiff::CharString.guess_encoding(file2_content)
|
|
185
|
-
case
|
|
186
|
-
when (encoding1 == "UNKNOWN" or encoding2 == "UNKNOWN")
|
|
187
|
-
raise "Document encoding unknown (#{encoding1}, #{encoding2})."
|
|
188
|
-
when encoding1 != encoding2
|
|
189
|
-
raise "Document encoding mismatch (#{encoding1}, #{encoding2})."
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
if docdiff.config[:eol] == "auto"
|
|
194
|
-
eol1 = DocDiff::CharString.guess_eol(file1_content)
|
|
195
|
-
eol2 = DocDiff::CharString.guess_eol(file2_content)
|
|
196
|
-
case
|
|
197
|
-
when (eol1.nil? or eol2.nil?)
|
|
198
|
-
raise "Document eol is nil (#{eol1.inspect}, #{eol2.inspect}). The document might be empty."
|
|
199
|
-
when (eol1 == 'UNKNOWN' or eol2 == 'UNKNOWN')
|
|
200
|
-
raise "Document eol unknown (#{eol1.inspect}, #{eol2.inspect})."
|
|
201
|
-
when (eol1 != eol2)
|
|
202
|
-
raise "Document eol mismatch (#{eol1}, #{eol2})."
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
doc1 = DocDiff::Document.new(file1_content, encoding1, eol1)
|
|
207
|
-
doc2 = DocDiff::Document.new(file2_content, encoding2, eol2)
|
|
208
|
-
|
|
209
|
-
output = docdiff.run(doc1, doc2,
|
|
210
|
-
{:resolution => docdiff.config[:resolution],
|
|
211
|
-
:format => docdiff.config[:format],
|
|
212
|
-
:digest => docdiff.config[:digest],
|
|
213
|
-
:display => docdiff.config[:display]})
|
|
214
|
-
docdiff.print_or_write_to_pager(output, docdiff.config[:pager])
|
|
7
|
+
DocDiff::CLI.run
|
data/doc/news.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# News
|
|
2
2
|
|
|
3
|
+
### 0.6.5 (2025-12-29)
|
|
4
|
+
|
|
5
|
+
* User-visible changes:
|
|
6
|
+
- Fixed label option not accumulating correctly.
|
|
7
|
+
- Fixed CRLF bug ([#57](https://github.com/hisashim/docdiff/issues/57)), where CRLFs in the input text are gobbled and not printed in the output when using `tty`, `wdiff`, and `user` formats. This problem seems to have existed since 0.3.
|
|
8
|
+
* Developer-related changes:
|
|
9
|
+
- Moved CLI-related stuff from `bin/docdiff` to `lib/docdiff/cli.rb`.
|
|
10
|
+
- Miscellaneous fixes and refactoring.
|
|
11
|
+
|
|
3
12
|
### 0.6.4 (2025-12-13)
|
|
4
13
|
|
|
5
14
|
* User-visible changes:
|
data/docdiff.gemspec
CHANGED
data/lib/doc_diff.rb
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
require 'docdiff/difference'
|
|
5
5
|
require 'docdiff/document'
|
|
6
6
|
require 'docdiff/view'
|
|
7
|
+
require 'docdiff/cli'
|
|
7
8
|
|
|
8
9
|
class DocDiff
|
|
9
10
|
|
|
10
|
-
AppVersion = Docdiff::VERSION
|
|
11
11
|
Author = "Copyright (C) 2002-2011 Hisashi MORITA.\n" +
|
|
12
12
|
"diff library originates from Ruby/CVS by TANAKA Akira.\n"
|
|
13
13
|
License = "This software is licensed under so-called modified BSD license.\n" +
|
|
@@ -21,34 +21,22 @@ class DocDiff
|
|
|
21
21
|
else
|
|
22
22
|
File.join(ENV['HOME'], ".config", "docdiff", "docdiff.conf")
|
|
23
23
|
end
|
|
24
|
+
DEFAULT_CONFIG = {
|
|
25
|
+
:resolution => "word",
|
|
26
|
+
:encoding => "auto",
|
|
27
|
+
:eol => "auto",
|
|
28
|
+
:format => "html",
|
|
29
|
+
:cache => true,
|
|
30
|
+
:digest => false,
|
|
31
|
+
:pager => nil,
|
|
32
|
+
:verbose => false
|
|
33
|
+
}
|
|
24
34
|
|
|
25
|
-
def initialize()
|
|
26
|
-
@config =
|
|
35
|
+
def initialize(config: {})
|
|
36
|
+
@config = config
|
|
27
37
|
end
|
|
28
38
|
attr_accessor :config
|
|
29
39
|
|
|
30
|
-
def DocDiff.parse_config_file_content(content)
|
|
31
|
-
result = {}
|
|
32
|
-
return result if content.size <= 0
|
|
33
|
-
lines = content.dup.split(/\r\n|\r|\n/).compact
|
|
34
|
-
lines.collect!{|line| line.sub(/#.*$/, '')}
|
|
35
|
-
lines.collect!{|line| line.strip}
|
|
36
|
-
lines.delete_if{|line| line == ""}
|
|
37
|
-
lines.each{|line|
|
|
38
|
-
raise 'line does not include " = ".' unless /[\s]+=[\s]+/.match line
|
|
39
|
-
name_src, value_src = line.split(/[\s]+=[\s]+/)
|
|
40
|
-
raise "Invalid name: #{name_src.inspect}" if (/\s/.match name_src)
|
|
41
|
-
raise "Invalid value: #{value_src.inspect}" unless value_src.kind_of?(String)
|
|
42
|
-
name = name_src.intern
|
|
43
|
-
value = value_src
|
|
44
|
-
value = true if ['on','yes','true'].include? value_src.downcase
|
|
45
|
-
value = false if ['off','no','false'].include? value_src.downcase
|
|
46
|
-
value = value_src.to_i if /^[0-9]+$/.match value_src
|
|
47
|
-
result[name] = value
|
|
48
|
-
}
|
|
49
|
-
result
|
|
50
|
-
end
|
|
51
|
-
|
|
52
40
|
def compare_by_line(doc1, doc2)
|
|
53
41
|
Difference.new(doc1.split_to_line, doc2.split_to_line)
|
|
54
42
|
end
|
|
@@ -162,26 +150,4 @@ class DocDiff
|
|
|
162
150
|
end
|
|
163
151
|
result.join
|
|
164
152
|
end
|
|
165
|
-
|
|
166
|
-
def process_config_file(filename)
|
|
167
|
-
file_content = nil
|
|
168
|
-
begin
|
|
169
|
-
File.open(filename, "r"){|f| file_content = f.read}
|
|
170
|
-
rescue Errno::ENOENT
|
|
171
|
-
message = "config file not found so not read."
|
|
172
|
-
ensure
|
|
173
|
-
if file_content != nil
|
|
174
|
-
self.config.update(DocDiff.parse_config_file_content(file_content))
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
message
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def print_or_write_to_pager(content, pager)
|
|
181
|
-
if STDOUT.tty? && pager.is_a?(String) && !pager.empty?
|
|
182
|
-
IO.popen(pager, "w"){|f| f.print content}
|
|
183
|
-
else
|
|
184
|
-
print content
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
153
|
end # class DocDiff
|
data/lib/docdiff/cli.rb
ADDED
|
@@ -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
|
data/lib/docdiff/version.rb
CHANGED
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|
|
|
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|
|
|
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)
|
|
@@ -212,7 +224,7 @@ class View
|
|
|
212
224
|
[]
|
|
213
225
|
end
|
|
214
226
|
TTYEscapeDic = {'ThisRandomString' => 'ThisRandomString'}
|
|
215
|
-
TTYEscapePat = /(
|
|
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 = {'<'=>'<', '>'=>'>', '&'=>'&', ' '=>' ',
|
|
279
291
|
"\r\n" => "<br />\r\n", "\r" => "<br />\r", "\n" => "<br />\n"}
|
|
280
|
-
HTMLEscapePat = /(
|
|
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,
|
|
@@ -387,7 +399,7 @@ class View
|
|
|
387
399
|
[]
|
|
388
400
|
end
|
|
389
401
|
WDIFFEscapeDic = {'ThisRandomString' => 'ThisRandomString'}
|
|
390
|
-
WDIFFEscapePat = /(
|
|
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 = /(
|
|
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,
|
data/test/cli_test.rb
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
# -*- coding: utf-8; -*-
|
|
3
|
+
|
|
4
|
+
require 'test/unit'
|
|
5
|
+
require 'nkf'
|
|
6
|
+
require 'docdiff/cli'
|
|
7
|
+
|
|
8
|
+
class TC_CLI < Test::Unit::TestCase
|
|
9
|
+
def test_parse_options!()
|
|
10
|
+
args = [
|
|
11
|
+
"--resolution=line",
|
|
12
|
+
"--char",
|
|
13
|
+
"--encoding=ASCII",
|
|
14
|
+
"--eucjp",
|
|
15
|
+
"--eol=CR",
|
|
16
|
+
"--crlf",
|
|
17
|
+
"--format=manued",
|
|
18
|
+
"--wdiff",
|
|
19
|
+
"--label=old",
|
|
20
|
+
"--label=new",
|
|
21
|
+
"--digest",
|
|
22
|
+
"--display=block",
|
|
23
|
+
"--pager='less --raw-control-chars'",
|
|
24
|
+
"--no-config-file",
|
|
25
|
+
"--config-file=./docdiff.conf",
|
|
26
|
+
"file1",
|
|
27
|
+
"file2",
|
|
28
|
+
]
|
|
29
|
+
expected = {
|
|
30
|
+
:resolution => "char",
|
|
31
|
+
:encoding => "EUC-JP",
|
|
32
|
+
:eol => "CRLF",
|
|
33
|
+
:format => "wdiff",
|
|
34
|
+
:digest => true,
|
|
35
|
+
:label => ["old", "new"],
|
|
36
|
+
:display => "block",
|
|
37
|
+
:pager => "'less --raw-control-chars'",
|
|
38
|
+
:no_config_file => true,
|
|
39
|
+
:config_file => "./docdiff.conf",
|
|
40
|
+
}
|
|
41
|
+
assert_equal(expected, DocDiff::CLI.parse_options!(args, base_options: {}))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_parse_config_file_content()
|
|
45
|
+
content = ["# comment line\n",
|
|
46
|
+
" # comment line with leading space\n",
|
|
47
|
+
"foo1 = bar\n",
|
|
48
|
+
"foo2 = bar baz \n",
|
|
49
|
+
" foo3 = 123 # comment\n",
|
|
50
|
+
"foo4 = no \n",
|
|
51
|
+
"foo1 = tRue\n",
|
|
52
|
+
"\n",
|
|
53
|
+
"",
|
|
54
|
+
nil].join
|
|
55
|
+
expected = {:foo1=>true, :foo2=>"bar baz", :foo3=>123, :foo4=>false}
|
|
56
|
+
assert_equal(expected, DocDiff::CLI.parse_config_file_content(content))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_read_config_from_file()
|
|
60
|
+
filename = File.join(File.dirname(__FILE__), "fixture/simple.conf")
|
|
61
|
+
expected = {:foo1 => true, :foo2 => "bar baz", :foo3 => 123, :foo4 => false}
|
|
62
|
+
config, _message = DocDiff::CLI.read_config_from_file(filename)
|
|
63
|
+
assert_equal(expected, config)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_read_config_from_file_raises_exception()
|
|
67
|
+
assert_raise(Errno::ENOENT) do
|
|
68
|
+
config, message = DocDiff::CLI.read_config_from_file("no/such/file")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_cli_resolution_line()
|
|
73
|
+
expected = <<~EOS.chomp
|
|
74
|
+
[-Hello, my name is Watanabe.
|
|
75
|
+
I am just another Ruby porter.
|
|
76
|
+
-]{+Hello, my name is matz.
|
|
77
|
+
It's me who has created Ruby. I am a Ruby hacker.
|
|
78
|
+
+}
|
|
79
|
+
EOS
|
|
80
|
+
cmd = "ruby -I lib bin/docdiff --resolution=line --format=wdiff" +
|
|
81
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
82
|
+
actual = `#{cmd}`
|
|
83
|
+
assert_equal(expected, actual)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_cli_resolution_word()
|
|
87
|
+
expected = <<~EOS
|
|
88
|
+
Hello, my name is [-Watanabe.-]{+matz.+}
|
|
89
|
+
{+It's me who has created Ruby. +}I am [-just another -]{+a +}Ruby [-porter.-]{+hacker.+}
|
|
90
|
+
EOS
|
|
91
|
+
cmd = "ruby -I lib bin/docdiff --resolution=word --format=wdiff" +
|
|
92
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
93
|
+
actual = `#{cmd}`
|
|
94
|
+
assert_equal(expected, actual)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_cli_resolution_char()
|
|
98
|
+
expected = <<~EOS
|
|
99
|
+
Hello, my name is [-W-]{+m+}at[-anabe-]{+z+}.
|
|
100
|
+
{+It's me who has created Ruby. +}I am [-just -]a[-nother-] Ruby [-port-]{+hack+}er.
|
|
101
|
+
EOS
|
|
102
|
+
cmd = "ruby -I lib bin/docdiff --resolution=char --format=wdiff" +
|
|
103
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
104
|
+
actual = `#{cmd}`
|
|
105
|
+
assert_equal(expected, actual)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_cli_encoding_ascii()
|
|
109
|
+
expected = <<~EOS
|
|
110
|
+
Hello, my name is [-Watanabe.-]{+matz.+}
|
|
111
|
+
{+It's me who has created Ruby. +}I am [-just another -]{+a +}Ruby [-porter.-]{+hacker.+}
|
|
112
|
+
EOS
|
|
113
|
+
cmd = "ruby -I lib bin/docdiff --encoding=ASCII --format=wdiff" +
|
|
114
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
115
|
+
actual = `#{cmd}`
|
|
116
|
+
assert_equal(expected, actual)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_cli_encoding_euc_jp()
|
|
120
|
+
expected = NKF.nkf("--ic=UTF-8 --oc=EUC-JP", <<~EOS)
|
|
121
|
+
[-こんにちは-]{+こんばんは+}、私の[-名前はわたなべです-]{+名前はまつもとです+}。
|
|
122
|
+
{+Rubyを作ったのは私です。+}私は[-Just Another -]Ruby [-Porter-]{+Hacker+}です。
|
|
123
|
+
EOS
|
|
124
|
+
cmd = "ruby --external-encoding EUC-JP -I lib bin/docdiff --encoding=EUC-JP --format=wdiff" +
|
|
125
|
+
" test/fixture/01_ja_eucjp_lf.txt test/fixture/02_ja_eucjp_lf.txt"
|
|
126
|
+
actual = `#{cmd}`.force_encoding("EUC-JP")
|
|
127
|
+
assert_equal(expected, actual)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def test_cli_encoding_shift_jis()
|
|
131
|
+
expected_utf8_cr =
|
|
132
|
+
"[-こんにちは-]{+こんばんは+}、私の[-名前はわたなべです-]{+名前はまつもとです+}。\r" +
|
|
133
|
+
"{+Rubyを作ったのは私です。+}私は[-Just Another -]Ruby [-Porter-]{+Hacker+}です。\r"
|
|
134
|
+
expected = NKF.nkf("--ic=UTF-8 --oc=Shift_JIS", expected_utf8_cr)
|
|
135
|
+
cmd = "ruby --external-encoding Shift_JIS -I lib bin/docdiff --encoding=Shift_JIS --format=wdiff" +
|
|
136
|
+
" test/fixture/01_ja_sjis_cr.txt test/fixture/02_ja_sjis_cr.txt"
|
|
137
|
+
actual = `#{cmd}`.force_encoding("Shift_JIS")
|
|
138
|
+
assert_equal(expected, actual)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_cli_encoding_utf_8()
|
|
142
|
+
expected = <<~EOS
|
|
143
|
+
[-こんにちは-]{+こんばんは+}、私の[-名前はわたなべです-]{+名前はまつもとです+}。
|
|
144
|
+
{+Rubyを作ったのは私です。+}私は[-Just Another -]Ruby [-Porter-]{+Hacker+}です。
|
|
145
|
+
EOS
|
|
146
|
+
cmd = "ruby -I lib bin/docdiff --encoding=UTF-8 --format=wdiff" +
|
|
147
|
+
" test/fixture/01_ja_utf8_lf.txt test/fixture/02_ja_utf8_lf.txt"
|
|
148
|
+
actual = `#{cmd}`.force_encoding("UTF-8")
|
|
149
|
+
assert_equal(expected, actual)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_cli_eol_cr()
|
|
153
|
+
expected =
|
|
154
|
+
"Hello, my name is [-Watanabe.-]{+matz.+}\r" +
|
|
155
|
+
"{+It's me who has created Ruby. +}I am [-just another -]{+a +}Ruby [-porter.-]{+hacker.+}\r"
|
|
156
|
+
cmd = "ruby -I lib bin/docdiff --eol=CR --format=wdiff" +
|
|
157
|
+
" test/fixture/01_en_ascii_cr.txt test/fixture/02_en_ascii_cr.txt"
|
|
158
|
+
actual = `#{cmd}`
|
|
159
|
+
assert_equal(expected, actual)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_cli_eol_lf()
|
|
163
|
+
expected =
|
|
164
|
+
"Hello, my name is [-Watanabe.-]{+matz.+}\n" +
|
|
165
|
+
"{+It's me who has created Ruby. +}I am [-just another -]{+a +}Ruby [-porter.-]{+hacker.+}\n"
|
|
166
|
+
cmd = "ruby -I lib bin/docdiff --eol=LF --format=wdiff" +
|
|
167
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
168
|
+
actual = `#{cmd}`
|
|
169
|
+
assert_equal(expected, actual)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def test_cli_eol_crlf()
|
|
173
|
+
expected =
|
|
174
|
+
"Hello, my name is [-Watanabe.-]{+matz.+}\r\n" +
|
|
175
|
+
"{+It's me who has created Ruby. +}I am [-just another -]{+a +}Ruby [-porter.-]{+hacker.+}\r\n"
|
|
176
|
+
cmd = "ruby -I lib bin/docdiff --eol=CRLF --format=wdiff" +
|
|
177
|
+
" test/fixture/01_en_ascii_crlf.txt test/fixture/02_en_ascii_crlf.txt"
|
|
178
|
+
actual = `#{cmd}`
|
|
179
|
+
assert_equal(expected, actual)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_cli_format_html()
|
|
183
|
+
expected = <<~EOS
|
|
184
|
+
<span class="common">Hello, my name is </span>\
|
|
185
|
+
<span class="before-change"><del>Watanabe.</del></span>\
|
|
186
|
+
<span class="after-change"><ins>matz.</ins></span>\
|
|
187
|
+
<span class="common"><br />
|
|
188
|
+
EOS
|
|
189
|
+
cmd = "ruby -I lib bin/docdiff --format=html" +
|
|
190
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
191
|
+
actual = `#{cmd}`.scan(/^.*?$\n/m)[-4]
|
|
192
|
+
assert_equal(expected, actual)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def test_cli_format_manued()
|
|
196
|
+
expected = "Hello, my name is [Watanabe./matz.]\n"
|
|
197
|
+
cmd = "ruby -I lib bin/docdiff --format=manued" +
|
|
198
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
199
|
+
actual = `#{cmd}`.scan(/^.*?$\n/m)[-2]
|
|
200
|
+
assert_equal(expected, actual)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def test_cli_format_tty()
|
|
204
|
+
expected = "Hello, my name is \e[7;4;33mWatanabe.\e[0m\e[7;1;32mmatz.\e[0m\n"
|
|
205
|
+
cmd = "ruby -I lib bin/docdiff --format=tty" +
|
|
206
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
207
|
+
actual = `#{cmd}`.scan(/^.*?$\n/m).first
|
|
208
|
+
assert_equal(expected, actual)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def test_cli_format_wdiff()
|
|
212
|
+
expected = "Hello, my name is [-Watanabe.-]{+matz.+}\n"
|
|
213
|
+
cmd = "ruby -I lib bin/docdiff --format=wdiff" +
|
|
214
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
215
|
+
actual = `#{cmd}`.scan(/^.*?$\n/m).first
|
|
216
|
+
assert_equal(expected, actual)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def test_cli_digest()
|
|
220
|
+
expected = <<~EOS
|
|
221
|
+
----
|
|
222
|
+
1,1
|
|
223
|
+
Hello, my name is [-Watanabe.-]{+matz.+}
|
|
224
|
+
|
|
225
|
+
----
|
|
226
|
+
(2),2
|
|
227
|
+
|
|
228
|
+
{+It's me who has created Ruby. +}I am
|
|
229
|
+
----
|
|
230
|
+
2,2
|
|
231
|
+
I am [-just another -]{+a +}Ruby
|
|
232
|
+
----
|
|
233
|
+
2,2
|
|
234
|
+
Ruby [-porter.-]{+hacker.+}
|
|
235
|
+
|
|
236
|
+
----
|
|
237
|
+
EOS
|
|
238
|
+
cmd = "ruby -I lib bin/docdiff --digest --format=wdiff" +
|
|
239
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
240
|
+
actual = `#{cmd}`.force_encoding("UTF-8")
|
|
241
|
+
assert_equal(expected, actual)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def test_cli_display_inline()
|
|
245
|
+
expected = <<~EOS
|
|
246
|
+
----
|
|
247
|
+
1,1
|
|
248
|
+
Hello, my name is [-Watanabe.-]{+matz.+}
|
|
249
|
+
|
|
250
|
+
----
|
|
251
|
+
(2),2
|
|
252
|
+
|
|
253
|
+
{+It's me who has created Ruby. +}I am
|
|
254
|
+
----
|
|
255
|
+
2,2
|
|
256
|
+
I am [-just another -]{+a +}Ruby
|
|
257
|
+
----
|
|
258
|
+
2,2
|
|
259
|
+
Ruby [-porter.-]{+hacker.+}
|
|
260
|
+
|
|
261
|
+
----
|
|
262
|
+
EOS
|
|
263
|
+
cmd = "ruby -I lib bin/docdiff --digest --display=inline --format=wdiff" +
|
|
264
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
265
|
+
actual = `#{cmd}`.force_encoding("UTF-8")
|
|
266
|
+
assert_equal(expected, actual)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def test_cli_display_block()
|
|
270
|
+
expected = <<~EOS
|
|
271
|
+
----
|
|
272
|
+
1,1
|
|
273
|
+
Hello, my name is [-Watanabe.-]
|
|
274
|
+
|
|
275
|
+
Hello, my name is {+matz.+}
|
|
276
|
+
|
|
277
|
+
----
|
|
278
|
+
(2),2
|
|
279
|
+
|
|
280
|
+
I am
|
|
281
|
+
|
|
282
|
+
{+It's me who has created Ruby. +}I am
|
|
283
|
+
----
|
|
284
|
+
2,2
|
|
285
|
+
I am [-just another -]Ruby
|
|
286
|
+
I am {+a +}Ruby
|
|
287
|
+
----
|
|
288
|
+
2,2
|
|
289
|
+
Ruby [-porter.-]
|
|
290
|
+
|
|
291
|
+
Ruby {+hacker.+}
|
|
292
|
+
|
|
293
|
+
----
|
|
294
|
+
EOS
|
|
295
|
+
cmd = "ruby -I lib bin/docdiff --digest --display=block --format=wdiff" +
|
|
296
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
297
|
+
actual = `#{cmd}`.force_encoding("UTF-8")
|
|
298
|
+
assert_equal(expected, actual)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def test_cli_config_file_format_wdiff()
|
|
302
|
+
config_file_name = File.join(File.dirname(__FILE__), "fixture/format_wdiff.conf")
|
|
303
|
+
expected = <<~EOS
|
|
304
|
+
Hello, my name is [-Watanabe.-]{+matz.+}
|
|
305
|
+
{+It's me who has created Ruby. +}I am [-just another -]{+a +}Ruby [-porter.-]{+hacker.+}
|
|
306
|
+
EOS
|
|
307
|
+
cmd = "ruby -I lib bin/docdiff --config-file=#{config_file_name}" +
|
|
308
|
+
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
309
|
+
actual = `#{cmd}`
|
|
310
|
+
assert_equal(expected, actual)
|
|
311
|
+
end
|
|
312
|
+
end
|
data/test/docdiff_test.rb
CHANGED
|
@@ -117,22 +117,6 @@ class TC_DocDiff < Test::Unit::TestCase
|
|
|
117
117
|
assert_equal(expected, docdiff.run(doc1, doc2, {:resolution => "char", :format => "manued", :digest => false}))
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
-
def test_parse_config_file_content()
|
|
121
|
-
content = ["# comment line\n",
|
|
122
|
-
" # comment line with leading space\n",
|
|
123
|
-
"foo1 = bar\n",
|
|
124
|
-
"foo2 = bar baz \n",
|
|
125
|
-
" foo3 = 123 # comment\n",
|
|
126
|
-
"foo4 = no \n",
|
|
127
|
-
"foo1 = tRue\n",
|
|
128
|
-
"\n",
|
|
129
|
-
"",
|
|
130
|
-
nil].join
|
|
131
|
-
expected = {:foo1=>true, :foo2=>"bar baz", :foo3=>123, :foo4=>false}
|
|
132
|
-
assert_equal(expected,
|
|
133
|
-
DocDiff.parse_config_file_content(content))
|
|
134
|
-
end
|
|
135
|
-
|
|
136
120
|
def test_run_line_user()
|
|
137
121
|
doc1 = Document.new("foo bar\nbaz", 'US-ASCII', 'LF')
|
|
138
122
|
doc2 = Document.new("foo beer\nbaz", 'US-ASCII', 'LF')
|
|
@@ -187,13 +171,6 @@ class TC_DocDiff < Test::Unit::TestCase
|
|
|
187
171
|
expected = "<=>foo </=><=>b</=><!->a</!-><!+>ee</!+><=>r</=><=>\n</=><=>baz</=>"
|
|
188
172
|
assert_equal(expected, docdiff.run(doc1, doc2, {:resolution => "char", :format => "user", :digest => false}))
|
|
189
173
|
end
|
|
190
|
-
def test_cli()
|
|
191
|
-
expected = "Hello, my name is [-Watanabe.-]{+matz.+}\n"
|
|
192
|
-
cmd = "ruby -I lib bin/docdiff --wdiff" +
|
|
193
|
-
" test/fixture/01_en_ascii_lf.txt test/fixture/02_en_ascii_lf.txt"
|
|
194
|
-
actual = `#{cmd}`.scan(/^.*?$\n/m).first
|
|
195
|
-
assert_equal(expected, actual)
|
|
196
|
-
end
|
|
197
174
|
|
|
198
175
|
def teardown()
|
|
199
176
|
#
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
format = wdiff
|
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docdiff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hisashi Morita
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-12-
|
|
11
|
-
dependencies:
|
|
10
|
+
date: 2025-12-29 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: test-unit
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3'
|
|
12
26
|
description: |-
|
|
13
27
|
DocDiff compares two text files and shows the
|
|
14
28
|
difference. It can compare files word by word,
|
|
@@ -56,6 +70,7 @@ files:
|
|
|
56
70
|
- lib/doc_diff.rb
|
|
57
71
|
- lib/docdiff.rb
|
|
58
72
|
- lib/docdiff/charstring.rb
|
|
73
|
+
- lib/docdiff/cli.rb
|
|
59
74
|
- lib/docdiff/diff.rb
|
|
60
75
|
- lib/docdiff/diff/contours.rb
|
|
61
76
|
- lib/docdiff/diff/editscript.rb
|
|
@@ -73,6 +88,7 @@ files:
|
|
|
73
88
|
- lib/docdiff/version.rb
|
|
74
89
|
- lib/docdiff/view.rb
|
|
75
90
|
- test/charstring_test.rb
|
|
91
|
+
- test/cli_test.rb
|
|
76
92
|
- test/diff_test.rb
|
|
77
93
|
- test/difference_test.rb
|
|
78
94
|
- test/docdiff_test.rb
|
|
@@ -93,8 +109,10 @@ files:
|
|
|
93
109
|
- test/fixture/02_ja_sjis_crlf.txt
|
|
94
110
|
- test/fixture/02_ja_utf8_crlf.txt
|
|
95
111
|
- test/fixture/02_ja_utf8_lf.txt
|
|
112
|
+
- test/fixture/format_wdiff.conf
|
|
96
113
|
- test/fixture/humpty_dumpty01_ascii_lf.txt
|
|
97
114
|
- test/fixture/humpty_dumpty02_ascii_lf.txt
|
|
115
|
+
- test/fixture/simple.conf
|
|
98
116
|
- test/test_helper.rb
|
|
99
117
|
- test/view_test.rb
|
|
100
118
|
homepage: https://github.com/hisashim/docdiff
|