ovec 0.0.2
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +10 -0
- data/TODO +9 -0
- data/bin/ovec +17 -0
- data/lib/ovec/nodes.rb +127 -0
- data/lib/ovec/parser.rb +169 -0
- data/lib/ovec/tex_manipulator.rb +29 -0
- data/lib/ovec/text_manipulator.rb +33 -0
- data/lib/ovec/tier.rb +56 -0
- data/lib/ovec/version.rb +3 -0
- data/lib/ovec.rb +9 -0
- data/ovec.gemspec +25 -0
- data/test/lib/ovec/parser.rb +77 -0
- data/test/lib/ovec/tex_manipulator.rb +39 -0
- data/test/lib/ovec/tier.rb +65 -0
- data/test/test_helper.rb +2 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bf233d8093609535f86f06d12e54d8d53ea44da0
|
4
|
+
data.tar.gz: 61af216b5f2b5bda2dafe0affd86d3991ac9b957
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae3384bf00f9d20dd2ab7cc398a70959a44454ed953c1f0ec9cbddb485fc06adeace43e1313baf8fdcea80f1bb7b45fa2459d7ec3437b65f7f981dc384858ddf
|
7
|
+
data.tar.gz: 11ffb6b266c098c33047a48962f461de4c602b77e5c9737739157f59816dc00632e12ac81b1a184016ea8b52876ce2e40893e8e809df9ac75455c51acab6643a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Michal Pokorný
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Ovec
|
2
|
+
|
3
|
+
Ovec is a linter for Czech TeX documents inspired by the capabilities of Vlna by Petr Olšák (http://petr.olsak.net/).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ovec'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ovec
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Use the installed binary `ovec` as a filter.
|
22
|
+
Right now, it writes out some debug messages to stderr - this will be fixed in a later version.
|
23
|
+
Usage example:
|
24
|
+
|
25
|
+
$ cat input.tex | ovec > output.tex
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
1. Fork it
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
33
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
- vlnky kolem promennych: graf~$X$, promenna~$Y$, ...
|
2
|
+
- \, v cislech misto mezery?
|
3
|
+
- ,~... (respektive \dots; nechci za tim sezrat mezeru - ???)
|
4
|
+
- varovat na ...?
|
5
|
+
- uvozovky (bacha na neparove). warn / correct?
|
6
|
+
- vlnkovadlo dle Olsaka.
|
7
|
+
- pozorovat ovlnkovany zdrojak.
|
8
|
+
- nevlnkovat:
|
9
|
+
- obecne makra, ve kterych se nevlnkuje
|
data/bin/ovec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ovec'
|
4
|
+
|
5
|
+
content = ARGF.read
|
6
|
+
|
7
|
+
parser = Ovec::Parser.new
|
8
|
+
tree = parser.parse(content)
|
9
|
+
|
10
|
+
tm = Ovec::TexManipulator.new
|
11
|
+
tm.bind(tree)
|
12
|
+
|
13
|
+
tier = Ovec::Tier.new
|
14
|
+
|
15
|
+
tm.run_text_manipulator(tier)
|
16
|
+
|
17
|
+
puts tree.to_tex
|
data/lib/ovec/nodes.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
module Ovec
|
2
|
+
class Node
|
3
|
+
end
|
4
|
+
|
5
|
+
class CommentNode < Node
|
6
|
+
def initialize(content)
|
7
|
+
@content = content
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_tex
|
11
|
+
content
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :content
|
15
|
+
end
|
16
|
+
|
17
|
+
class TextCommandsNode < Node
|
18
|
+
# If verbatim, do not interpret content as actual text.
|
19
|
+
def initialize(command, content, text: true)
|
20
|
+
@command = command
|
21
|
+
@content = content
|
22
|
+
@text = text
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_tex
|
26
|
+
"\\#@command{#{@content.to_tex}}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def text?; @text; end
|
30
|
+
|
31
|
+
attr_reader :command
|
32
|
+
attr_reader :content
|
33
|
+
end
|
34
|
+
|
35
|
+
class VerbatimNode < Node
|
36
|
+
def initialize(command, delimiter, content)
|
37
|
+
@command = command
|
38
|
+
@delimiter = delimiter
|
39
|
+
@content = content
|
40
|
+
end
|
41
|
+
|
42
|
+
def left_delimiter
|
43
|
+
@delimiter
|
44
|
+
end
|
45
|
+
|
46
|
+
def right_delimiter
|
47
|
+
Parser.delimiter_right_side @delimiter
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_tex
|
51
|
+
"\\#@command#{left_delimiter}#@content#{right_delimiter}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class CommandsNode < Node
|
56
|
+
def initialize(content)
|
57
|
+
@content = content
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_tex
|
61
|
+
"\\#{content}"
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_reader :content
|
65
|
+
end
|
66
|
+
|
67
|
+
class MathNode < Node
|
68
|
+
INLINE = :inline
|
69
|
+
DISPLAY = :display
|
70
|
+
|
71
|
+
def initialize(type, content)
|
72
|
+
@type = type
|
73
|
+
@content = content
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_tex
|
77
|
+
if type == INLINE
|
78
|
+
"$#{content}$"
|
79
|
+
else
|
80
|
+
"$$#{content}$$"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_reader :type
|
85
|
+
attr_reader :content
|
86
|
+
end
|
87
|
+
|
88
|
+
class TextNode < Node
|
89
|
+
def initialize(text)
|
90
|
+
@text = text
|
91
|
+
end
|
92
|
+
|
93
|
+
def length
|
94
|
+
text.length
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_tex
|
98
|
+
text
|
99
|
+
end
|
100
|
+
|
101
|
+
attr_reader :text
|
102
|
+
end
|
103
|
+
|
104
|
+
class CombinedNode < Node
|
105
|
+
def initialize(content = [])
|
106
|
+
@content = content
|
107
|
+
end
|
108
|
+
|
109
|
+
def <<(thing)
|
110
|
+
@content << thing
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_tex()
|
114
|
+
content.map(&:to_tex).join('')
|
115
|
+
end
|
116
|
+
|
117
|
+
def length
|
118
|
+
@content.length
|
119
|
+
end
|
120
|
+
|
121
|
+
def [](index)
|
122
|
+
@content[index]
|
123
|
+
end
|
124
|
+
|
125
|
+
attr_reader :content
|
126
|
+
end
|
127
|
+
end
|
data/lib/ovec/parser.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
module Ovec
|
2
|
+
class ParseError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
# TODO: verbatimy; TeX prikazy obsahujici kusy k ovlnkovani
|
6
|
+
|
7
|
+
class Parser
|
8
|
+
NORMAL_REGEX = /\A([^\\$%])+/
|
9
|
+
# TODO: more escapes: there are probably 10x more...
|
10
|
+
COMMAND_REGEX = /\A\\([a-zA-Z0-9]+|[%:,\\])/
|
11
|
+
COMMENT_REGEX = /\A%.*$/
|
12
|
+
|
13
|
+
TEXT_COMMANDS = %w(textit textbf textsc title author)
|
14
|
+
# TODO: seznam prikazu, ve kterych se nevlnkuje
|
15
|
+
|
16
|
+
NONTEXT_CONTENT_COMMANDS = %w() # TODO: treba \begin, \end, ...
|
17
|
+
|
18
|
+
VERBATIM_COMMANDS = %w(verbatim verb)
|
19
|
+
|
20
|
+
private
|
21
|
+
def debug(*args)
|
22
|
+
$stderr.puts(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_other_side(string, start, left = '{', right = '}')
|
26
|
+
level = 0
|
27
|
+
for i in start...string.length
|
28
|
+
if string[i] == left && (right != left || i == start)
|
29
|
+
level += 1
|
30
|
+
elsif string[i] == right
|
31
|
+
level -= 1
|
32
|
+
end
|
33
|
+
|
34
|
+
return i if level == 0
|
35
|
+
end
|
36
|
+
|
37
|
+
raise ParseError, "No matching #{right} found for #{left} in #{string} from index #{start}."
|
38
|
+
end
|
39
|
+
|
40
|
+
public
|
41
|
+
def self.delimiter_right_side(left)
|
42
|
+
{
|
43
|
+
'{' => '}', '[' => ']', '(' => ')', '<' => '>'
|
44
|
+
}[left] || left
|
45
|
+
end
|
46
|
+
|
47
|
+
def eat_node(code, index)
|
48
|
+
to_eat = code[index...code.length]
|
49
|
+
|
50
|
+
if to_eat.empty?
|
51
|
+
raise ParseError, "Parsing an empty string"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Cut off comments
|
55
|
+
match = COMMENT_REGEX.match(to_eat)
|
56
|
+
unless match.nil?
|
57
|
+
match = match[0]
|
58
|
+
node = CommentNode.new(match)
|
59
|
+
|
60
|
+
debug "Eaten #{match.length} chars of comments."
|
61
|
+
return node, match.length
|
62
|
+
end
|
63
|
+
|
64
|
+
# Cut off normal text
|
65
|
+
match = NORMAL_REGEX.match(to_eat)
|
66
|
+
unless match.nil?
|
67
|
+
match = match[0]
|
68
|
+
node = TextNode.new(match)
|
69
|
+
|
70
|
+
debug "Eaten #{match.length} chars of normal text."
|
71
|
+
return node, match.length
|
72
|
+
end
|
73
|
+
|
74
|
+
# Parse a command. If the command looks like a text-command, try to eat everything between { and }.
|
75
|
+
match = COMMAND_REGEX.match(to_eat)
|
76
|
+
unless match.nil?
|
77
|
+
command = match[1]
|
78
|
+
match_len = match[0].length
|
79
|
+
debug "Eating command #{command}."
|
80
|
+
|
81
|
+
if TEXT_COMMANDS.include?(command) || NONTEXT_CONTENT_COMMANDS.include?(command)
|
82
|
+
if to_eat.length > match_len && to_eat[match_len] == '{'
|
83
|
+
debug "Looks like a text command, trying to parse recursively."
|
84
|
+
begin
|
85
|
+
other_side = find_other_side(to_eat, match_len, '{', '}')
|
86
|
+
|
87
|
+
content = parse(code[index + match_len + 1...index + other_side])
|
88
|
+
# TODO: rename textcommands to something better: command with params?
|
89
|
+
node = TextCommandsNode.new(command, content, text: TEXT_COMMANDS.include?(command))
|
90
|
+
|
91
|
+
debug "Eaten #{other_side + 1} chars of text command."
|
92
|
+
return node, (other_side + 1)
|
93
|
+
rescue ParseError
|
94
|
+
debug "Parse error encountered. Giving up on this node."
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if VERBATIM_COMMANDS.include?(command)
|
100
|
+
if to_eat.length <= match_len
|
101
|
+
raise ParseError, "Verbatim at EOF with no delimiter."
|
102
|
+
end
|
103
|
+
|
104
|
+
left_delimiter = to_eat[match_len]
|
105
|
+
right_delimiter = self.class.delimiter_right_side(left_delimiter)
|
106
|
+
|
107
|
+
# TODO: nesting in verbatims ???
|
108
|
+
other_side = find_other_side(to_eat, match_len, left_delimiter, right_delimiter)
|
109
|
+
|
110
|
+
content = to_eat[match_len + 1...other_side]
|
111
|
+
|
112
|
+
node = VerbatimNode.new(command, left_delimiter, content)
|
113
|
+
|
114
|
+
debug "Eaten #{other_side + 1} chars of verbatim."
|
115
|
+
return node, (other_side + 1)
|
116
|
+
end
|
117
|
+
|
118
|
+
node = CommandsNode.new(command)
|
119
|
+
|
120
|
+
debug "Eaten #{match_len} chars of commands."
|
121
|
+
return node, match_len
|
122
|
+
end
|
123
|
+
|
124
|
+
if to_eat.length > 1 && to_eat[0..1] == "$$"
|
125
|
+
# Lookaround assertion to handle escaped dollars in math.
|
126
|
+
ending = to_eat[2...to_eat.length].index(/(?<!\\)\$\$/)
|
127
|
+
|
128
|
+
if ending.nil?
|
129
|
+
raise ParseError, "Unterminated $$ starting at #{index}"
|
130
|
+
end
|
131
|
+
|
132
|
+
node = MathNode.new(MathNode::DISPLAY, to_eat[2...ending+2])
|
133
|
+
eaten = ending + 4
|
134
|
+
debug "Eaten #{eaten} chars of inline math."
|
135
|
+
return node, eaten
|
136
|
+
end
|
137
|
+
|
138
|
+
if to_eat[0] == '$'
|
139
|
+
# Lookaround assertion to handle escaped dollars in math.
|
140
|
+
ending = to_eat[1...to_eat.length].index(/(?<!\\)\$/)
|
141
|
+
|
142
|
+
if ending.nil?
|
143
|
+
raise ParseError, "Unterminated $ starting at #{index}"
|
144
|
+
end
|
145
|
+
|
146
|
+
node = MathNode.new(MathNode::INLINE, to_eat[1...ending+1])
|
147
|
+
eaten = ending + 2
|
148
|
+
debug "Eaten #{eaten} chars of inline math."
|
149
|
+
return node, eaten
|
150
|
+
end
|
151
|
+
|
152
|
+
raise ParseError, "Don't know how to parse '#{to_eat}'."
|
153
|
+
end
|
154
|
+
|
155
|
+
def parse(code)
|
156
|
+
result = CombinedNode.new
|
157
|
+
|
158
|
+
index = 0
|
159
|
+
while index < code.length
|
160
|
+
node, shift = eat_node(code, index)
|
161
|
+
index += shift
|
162
|
+
|
163
|
+
result << node
|
164
|
+
end
|
165
|
+
|
166
|
+
return result
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Ovec
|
2
|
+
class TexManipulator
|
3
|
+
def bind(root)
|
4
|
+
@root = root
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
def load_text_chunks_dfs(node)
|
9
|
+
case node
|
10
|
+
when Ovec::TextCommandsNode then
|
11
|
+
load_text_chunks_dfs(node.content) if node.text?
|
12
|
+
when Ovec::TextNode then
|
13
|
+
@text_chunks << node.text
|
14
|
+
when Ovec::CombinedNode then
|
15
|
+
node.content.each { |subnode|
|
16
|
+
load_text_chunks_dfs(subnode)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
public
|
22
|
+
def run_text_manipulator(manipulator)
|
23
|
+
@text_chunks = []
|
24
|
+
load_text_chunks_dfs(@root)
|
25
|
+
manipulator.bind(@text_chunks)
|
26
|
+
manipulator.run
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Ovec
|
2
|
+
class TextManipulator
|
3
|
+
# Subclasses, note: once you change the length of a chunk, delimiters will no longer be correct.
|
4
|
+
|
5
|
+
def bind(chunks)
|
6
|
+
@chunks = chunks
|
7
|
+
@delimiters = [0]
|
8
|
+
chunks.each do |chunk|
|
9
|
+
@delimiters << @delimiters.last + chunk.length
|
10
|
+
end
|
11
|
+
|
12
|
+
_rejoin
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
# Given an offset in the original string, returns the chunk and chunk offset
|
18
|
+
# that contains the refers to the same character.
|
19
|
+
def _find_chunk_and_offset(offset)
|
20
|
+
j = 0
|
21
|
+
while j < @delimiters.size - 1
|
22
|
+
break if @delimiters[j + 1] > offset
|
23
|
+
j += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
return [ @chunks[j], offset - @delimiters[j] ]
|
27
|
+
end
|
28
|
+
|
29
|
+
def _rejoin
|
30
|
+
@joined = @chunks.join('')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/ovec/tier.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
|
3
|
+
module Ovec
|
4
|
+
class Tier < TextManipulator
|
5
|
+
# The last character this regex matches is changed to a tilde.
|
6
|
+
REGEX = /(
|
7
|
+
((\p{Z}|\~|\n)[KkSsVvZzOoUu]\p{Z})| # KSVZOU jako samostatne slovo
|
8
|
+
([\.\?\!](\p{Z}|\~)+[KSVZOUAI]\p{Z})| # KSVZOUAI na zacatku vety
|
9
|
+
(\A[KSVZOUAI]\p{Z})| # KSVZOUAI na zacatku textu
|
10
|
+
(\p{Z}(?=--(\p{Z}|\n)))| # mezera, za kterou je pomlcka
|
11
|
+
(,(\p{Z}|\~|\n)+a\p{Z}) # ... modulo 10, a~timto prvkem ...; TODO: plati tohle i pro "i"?
|
12
|
+
)/x
|
13
|
+
|
14
|
+
# TODO: generally tie "5.~batalion", ...
|
15
|
+
# All changes within this regex are changed to a tilde.
|
16
|
+
DATE_REGEX = /(
|
17
|
+
(?<=\p{Z})\p{Nd}{1,2}\.\p{Z}
|
18
|
+
(\p{Nd}{1,2}\.|leden|únor|březen|duben|květen|červen|červenec|srpen|září|říjen|listopad|prosinec| # TODO: plne sklonovani? nebo nejaky wildcard?
|
19
|
+
ledna|února|března|dubna|května|června|července|srpna|září|října|listopadu|prosince)\p{Z}
|
20
|
+
\p{Nd}{4}(?=\p{Z}) # Datum jako "1. 5. 2013"
|
21
|
+
)/x
|
22
|
+
|
23
|
+
def run
|
24
|
+
# ~ neni mezi \p{Z}.
|
25
|
+
# TODO: moznosti na dvojpismenne predlozky
|
26
|
+
matches = @joined.to_enum(:scan, REGEX).map { Regexp.last_match }
|
27
|
+
|
28
|
+
# Matches may overlap, find all matches with more scans.
|
29
|
+
# TODO: optimize
|
30
|
+
until matches.empty?
|
31
|
+
for i in 0...matches.length
|
32
|
+
# TODO: check ze za tim neni tilda nikde (napr. na dalsim radku, par mezer pozdeji, ...)
|
33
|
+
match = matches[i]
|
34
|
+
change = match.end(0) - 1
|
35
|
+
chunk, offset = _find_chunk_and_offset(change)
|
36
|
+
chunk[offset] = '~'
|
37
|
+
end
|
38
|
+
|
39
|
+
_rejoin
|
40
|
+
matches = @joined.to_enum(:scan, REGEX).map { Regexp.last_match }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Dates can't overlap. 1 scan is enough.
|
44
|
+
matches = @joined.to_enum(:scan, DATE_REGEX).map { Regexp.last_match }
|
45
|
+
for i in 0...matches.length
|
46
|
+
match = matches[i]
|
47
|
+
for j in match.begin...match.end
|
48
|
+
if @joined[j] == ' '
|
49
|
+
chunk, offset = _find_chunk_and_offset(j)
|
50
|
+
chunk[offset] = '~'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/ovec/version.rb
ADDED
data/lib/ovec.rb
ADDED
data/ovec.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ovec/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ovec"
|
8
|
+
spec.version = Ovec::VERSION
|
9
|
+
spec.authors = ["Michal Pokorný"]
|
10
|
+
spec.email = ["pok@rny.cz"]
|
11
|
+
spec.description = %q{A TeX linter for the Czech language.}
|
12
|
+
spec.summary = %q{Ovec inserts nonbreakable spaces into your Czech language TeX files.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.required_ruby_version = ">= 2"
|
25
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module Ovec
|
4
|
+
class ParserTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@parser = Parser.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_basic_text_parsing
|
10
|
+
text = "Hello world!"
|
11
|
+
node, eaten = @parser.eat_node(text, 0)
|
12
|
+
assert_equal text.length, eaten
|
13
|
+
assert node.is_a?(TextNode)
|
14
|
+
assert_equal text.length, node.length
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_basic_math_parsing
|
18
|
+
text = "$10+5=7$"
|
19
|
+
node, eaten = @parser.eat_node(text, 0)
|
20
|
+
assert_equal text.length, eaten
|
21
|
+
assert node.is_a?(MathNode)
|
22
|
+
assert_equal node.type, MathNode::INLINE
|
23
|
+
assert_equal node.content, text[1...(text.length - 1)]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_basic_display_math_parsing
|
27
|
+
text = "$$10+5=7$$"
|
28
|
+
node, eaten = @parser.eat_node(text, 0)
|
29
|
+
assert_equal text.length, eaten
|
30
|
+
assert node.is_a?(MathNode)
|
31
|
+
assert_equal node.type, MathNode::DISPLAY
|
32
|
+
assert_equal node.content, text[2...(text.length - 2)]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_basic_composite_parsing
|
36
|
+
text = "Ahoj světe. $10 + 5 = 3$\n\n\tSbohem. $$X \geq 4$$"
|
37
|
+
result = @parser.parse(text)
|
38
|
+
assert result.is_a?(CombinedNode)
|
39
|
+
assert_equal text, result.to_tex
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_escaped_dollar_math
|
43
|
+
input = "$$Ahoj\\$$Svete$$"
|
44
|
+
result = @parser.parse(input)
|
45
|
+
assert result.length == 1
|
46
|
+
assert result[0].is_a? MathNode
|
47
|
+
assert result[0].content == "Ahoj\\$$Svete"
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_text_command_parsing
|
51
|
+
text = '\textbf{Ahoj. Tohle je \textit{text}, ktery se bude vlnkovat.}'
|
52
|
+
result = @parser.parse(text)
|
53
|
+
assert result.is_a?(CombinedNode)
|
54
|
+
assert result.length == 1
|
55
|
+
|
56
|
+
node = result[0]
|
57
|
+
assert node.is_a?(TextCommandsNode)
|
58
|
+
assert node.command == "textbf"
|
59
|
+
|
60
|
+
node2 = node.content
|
61
|
+
assert node2.is_a?(CombinedNode)
|
62
|
+
assert node2.length == 3
|
63
|
+
assert node2[0].is_a?(TextNode)
|
64
|
+
assert node2[0].text == "Ahoj. Tohle je "
|
65
|
+
assert node2[1].is_a?(TextCommandsNode)
|
66
|
+
assert node2[1].command == "textit"
|
67
|
+
assert node2[1].content[0].text == "text"
|
68
|
+
assert node2[2].text == ", ktery se bude vlnkovat."
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_command_parsed
|
72
|
+
text = '\blabla'
|
73
|
+
result = @parser.parse(text)
|
74
|
+
assert_equal text, result.to_tex
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module Ovec
|
4
|
+
class TexManipulatorTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@tm = TexManipulator.new
|
7
|
+
@tier = Tier.new
|
8
|
+
@parser = Parser.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_commands_reversible
|
12
|
+
tex = '\heading{Ahoj}'
|
13
|
+
tree = @parser.parse(tex)
|
14
|
+
assert_equal tex, tree.to_tex
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def assert_ties_to(input, output)
|
19
|
+
tree = @parser.parse(input)
|
20
|
+
assert tree.is_a?(CombinedNode)
|
21
|
+
@tm.bind(tree)
|
22
|
+
@tm.run_text_manipulator(@tier)
|
23
|
+
assert_equal output, tree.to_tex
|
24
|
+
end
|
25
|
+
|
26
|
+
public
|
27
|
+
def test_basic_tex
|
28
|
+
input = 'Ahoj svete. \\textbf{I v ceskych luzich dochazi k ubytku s\\textit{ prichodem} podzimu.}'
|
29
|
+
output = 'Ahoj svete. \\textbf{I~v~ceskych luzich dochazi k~ubytku s\\textit{~prichodem} podzimu.}'
|
30
|
+
assert_ties_to input, output
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_with_verb
|
34
|
+
input = 'Ahoj. \verb|Tohle % je ve verbatimu, takze to neni komentar. V tomhle se nevlnkuje.| V tomhle kusu se uz zase vlnkuje.'
|
35
|
+
output = 'Ahoj. \verb|Tohle % je ve verbatimu, takze to neni komentar. V tomhle se nevlnkuje.| V~tomhle kusu se uz zase vlnkuje.'
|
36
|
+
assert_ties_to input, output
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative '../../test_helper'
|
2
|
+
|
3
|
+
module Ovec
|
4
|
+
class TierTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@tier = Tier.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_basic_without_ties
|
10
|
+
text = "Ahoj. Jak se máš?"
|
11
|
+
text_duplicate = text.dup
|
12
|
+
@tier.bind([text_duplicate])
|
13
|
+
@tier.run
|
14
|
+
assert_equal text, text_duplicate
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def assert_ties_to(input, output)
|
19
|
+
input = [input] if input.is_a? String
|
20
|
+
output = [output] if output.is_a? String
|
21
|
+
text = input.dup
|
22
|
+
@tier.bind(test)
|
23
|
+
@tier.run
|
24
|
+
assert_equal text, outpu
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_simple_tie
|
28
|
+
assert_ties_to "K blabla u blabla s blabla.", "K~blabla u~blabla s~blabla."
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_array_tie
|
32
|
+
assert_ties_to [ "K blabla u", " blabla ", "s blabla.", " A blabla?" ], [ "K~blabla u", "~blabla ", "s~blabla.", " A~blabla?" ]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_regex_works
|
36
|
+
regex = Tier::REGEX
|
37
|
+
assert !("ahoj" =~ regex)
|
38
|
+
assert "~v " =~ regex
|
39
|
+
assert "Cau. A hrdy bud, zes vytrval." =~ regex
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_hard_tie
|
43
|
+
assert_ties_to "I v ceskych luzich dochazi k ubytku s prichodem podzimu.",
|
44
|
+
"I~v~ceskych luzich dochazi k~ubytku s~prichodem podzimu."
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_tie_dash
|
48
|
+
assert_ties_to "Ach -- pomlcka!", "Ach~-- pomlcka!"
|
49
|
+
assert_ties_to "Ach --\npomlcka!", "Ach~--\npomlcka!"
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_tie_across_newline
|
53
|
+
assert_ties_to "Pojednani pojednavajici\no pojednavani.", "Pojednavani pojednavajici\no~pojednavani."
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_tie_a_after_pause
|
57
|
+
assert_ties_to "Kone se pasou, a ovce se pasou.", "Kone se pasou, a~ovce se pasou."
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_tie_date
|
61
|
+
assert_ties_to "3. 4. 2012", "3.~4.~2012"
|
62
|
+
assert_ties_to "1. leden 1950", "1.~leden~1950"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ovec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michal Pokorný
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: A TeX linter for the Czech language.
|
42
|
+
email:
|
43
|
+
- pok@rny.cz
|
44
|
+
executables:
|
45
|
+
- ovec
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- .gitignore
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- TODO
|
55
|
+
- bin/ovec
|
56
|
+
- lib/ovec.rb
|
57
|
+
- lib/ovec/nodes.rb
|
58
|
+
- lib/ovec/parser.rb
|
59
|
+
- lib/ovec/tex_manipulator.rb
|
60
|
+
- lib/ovec/text_manipulator.rb
|
61
|
+
- lib/ovec/tier.rb
|
62
|
+
- lib/ovec/version.rb
|
63
|
+
- ovec.gemspec
|
64
|
+
- test/lib/ovec/parser.rb
|
65
|
+
- test/lib/ovec/tex_manipulator.rb
|
66
|
+
- test/lib/ovec/tier.rb
|
67
|
+
- test/test_helper.rb
|
68
|
+
homepage: ''
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
metadata: {}
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '2'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 2.0.0
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: Ovec inserts nonbreakable spaces into your Czech language TeX files.
|
92
|
+
test_files:
|
93
|
+
- test/lib/ovec/parser.rb
|
94
|
+
- test/lib/ovec/tex_manipulator.rb
|
95
|
+
- test/lib/ovec/tier.rb
|
96
|
+
- test/test_helper.rb
|