typogrowth 0.9.0
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/.document +11 -0
- data/.gitignore +18 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +61 -0
- data/Rakefile +19 -0
- data/bin/typo +35 -0
- data/features/step_definitions/typogrowth_steps.rb +40 -0
- data/features/support/env.rb +5 -0
- data/features/typogrowth.feature +54 -0
- data/lib/config/typogrowth.yaml +132 -0
- data/lib/typogrowth/string.rb +17 -0
- data/lib/typogrowth/version.rb +3 -0
- data/lib/typogrowth.rb +145 -0
- data/lib/utils/hash_recursive_merge.rb +73 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/test.input +24 -0
- data/spec/typogrowth_spec.rb +7 -0
- data/typogrowth.gemspec +38 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: 8b9cc2df68d09b41d6e66c2f23267d983b3de918
|
4
|
+
data.tar.gz: e941117481606671b6f2761e587d2f52d7ed7561
|
5
|
+
!binary "U0hBNTEy":
|
6
|
+
metadata.gz: 3a46dceca2914e0efb637b78f54d850e8426f860127aeb6d50da8f79b5c0a45a6c4ffd358632ad9578050513aa92b35cc2bee2429ad34b1b9540d6f86f39b9b3
|
7
|
+
data.tar.gz: 23d646481747e78b23e29ae2360854aeb3b3849a773f535d5426dae39f94c85ce81cb392a024cae8f9bbd9d90f7ca40e16d2ce0af765687e1349f2df65a85cd0
|
data/.document
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# .document is used by rdoc and yard to know how to generate documentation
|
2
|
+
# for example, it can be used to control how rdoc gets built when you do `gem install foo`
|
3
|
+
|
4
|
+
README.rdoc
|
5
|
+
lib/**/*.rb
|
6
|
+
bin/*
|
7
|
+
|
8
|
+
# Files below this - are treated as 'extra files', and aren't parsed for ruby code
|
9
|
+
-
|
10
|
+
features/**/*.feature
|
11
|
+
LICENSE
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Alexei Matyushkin
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Typogrowth
|
2
|
+
|
3
|
+
[](https://travis-ci.org/mudasobwa/typogrowth)
|
4
|
+
[](https://gemnasium.com/mudasobwa/typogrowth)
|
5
|
+
|
6
|
+
Typogrowth is the simple gem, providing easy way to make string
|
7
|
+
typographically correct. It introduce the class method:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
Typogrowth.parse string, lang = nil
|
11
|
+
```
|
12
|
+
|
13
|
+
as well as it monkeypatches `String` class with `typo` method.
|
14
|
+
If language is omitted, it uses `I18n.locale`. Also `:default`
|
15
|
+
may be specified as language setting (which is english, in fact.)
|
16
|
+
|
17
|
+
To modify the succession of quotation signs (as well as all the
|
18
|
+
others options,) feel free to change `config/typogrowth.yaml`.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
gem 'typogrowth'
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
$ bundle
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
$ gem install typogrowth
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
s = 'And God said "Baz heard "Bar" once" , and there was light.'
|
38
|
+
puts s.typo
|
39
|
+
# ⇒ And God said “Baz heard ‘Bar’ once,” and there was light.
|
40
|
+
puts Typogrowth.parse(s)
|
41
|
+
# ⇒ And God said “Baz heard ‘Bar’ once,” and there was light.
|
42
|
+
|
43
|
+
s = 'И Бог сказал: "Я - слышу "Бум" и "Бам" где-то там" , и стало светло.'
|
44
|
+
puts s.typo('ru') # Explicit locale specification may be omitted
|
45
|
+
# while running under ru_RU.UTF-8 locale
|
46
|
+
# ⇒ И Бог сказал: «Я — слышу „Бум“ и „Бам“ где-то там», и стало светло.
|
47
|
+
|
48
|
+
s = 'And God said "Oslo coordinates are: 59°57′N 10°45′E" and there was light.'
|
49
|
+
s.typo!
|
50
|
+
# ⇒ And God said “Oslo coordinates are: 59°57′N 10°45′E” and there was light.
|
51
|
+
puts s
|
52
|
+
# ⇒ And God said “Oslo coordinates are: 59°57′N 10°45′E” and there was light.
|
53
|
+
```
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
1. Fork it
|
58
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
59
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
60
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
61
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
RSpec::Core::RakeTask.new(:examples) do |examples|
|
5
|
+
examples.rspec_opts = '-Ispec'
|
6
|
+
end
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
9
|
+
spec.rspec_opts = '-Ispec'
|
10
|
+
spec.rcov = true
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'cucumber/rake/task'
|
14
|
+
Cucumber::Rake::Task.new(:features)
|
15
|
+
|
16
|
+
task :default => :features
|
17
|
+
|
18
|
+
require 'yard'
|
19
|
+
YARD::Rake::YardocTask.new
|
data/bin/typo
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require_relative '../lib/typogrowth'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: #{$0} FILE|STRING"
|
12
|
+
|
13
|
+
# Bowl result?
|
14
|
+
opts.on("-o", "--out FILE",
|
15
|
+
"Output file name for result") do |outfile|
|
16
|
+
options[:outfile] = outfile
|
17
|
+
end
|
18
|
+
|
19
|
+
# No argument, shows at tail. This will print an options summary.
|
20
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
21
|
+
puts opts
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
end.parse!
|
25
|
+
|
26
|
+
raise "Run `#{$0} --help` for execution examples. Exiting…" if ARGV.size.zero?
|
27
|
+
|
28
|
+
file_or_string = ARGV.first
|
29
|
+
file_or_string = File.read(file_or_string) if File.exist?(file_or_string)
|
30
|
+
|
31
|
+
@result = Typogrowth::parse file_or_string
|
32
|
+
|
33
|
+
options[:outfile].nil? ?
|
34
|
+
puts(@result) :
|
35
|
+
File.write(options[:outfile], @result)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Given(/^the input string is "(.*?)"$/) do |str|
|
4
|
+
@content = str
|
5
|
+
end
|
6
|
+
|
7
|
+
When(/^input string is processed with Typogrowl’s typography parser$/) do
|
8
|
+
@content.gsub! /\\+"/, '"'
|
9
|
+
@typo = Typogrowth.parse @content
|
10
|
+
end
|
11
|
+
|
12
|
+
When(/^input string is processed with Typogrowl’s typography parser with lang "(.*?)"$/) do |lang|
|
13
|
+
@content.gsub! /\\+"/, '"'
|
14
|
+
@typo = Typogrowth.parse @content, lang
|
15
|
+
end
|
16
|
+
|
17
|
+
When(/^input string is modified inplace with typo!$/) do
|
18
|
+
@typoed = @content.dup
|
19
|
+
@typoed.typo!
|
20
|
+
end
|
21
|
+
|
22
|
+
Then(/^neither single nor double quotes are left in the string$/) do
|
23
|
+
@typo.scan(/"|'/).count.should == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
Then(/^the typoed result should equal to "(.*?)"$/) do |str|
|
27
|
+
@typo.should == str
|
28
|
+
end
|
29
|
+
|
30
|
+
Then(/^the call to string’s typo should equal to "(.*?)"$/) do |str|
|
31
|
+
@content.typo.should == str
|
32
|
+
end
|
33
|
+
|
34
|
+
Then(/^the call to string’s typo with lang "(.*?)" should equal to "(.*?)"$/) do |lang, str|
|
35
|
+
@content.typo('ru').should == str
|
36
|
+
end
|
37
|
+
|
38
|
+
Then(/^typoed result should equal to "(.*?)"$/) do |str|
|
39
|
+
@typoed.should == str
|
40
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
Feature: Text is to be typographed (spacing and pubctuation are to be sanitized)
|
2
|
+
|
3
|
+
# Known bug: see last example in first scenario outline
|
4
|
+
Scenario Outline: Quotes and punctuation in English
|
5
|
+
Given the input string is <input>
|
6
|
+
When input string is processed with Typogrowl’s typography parser
|
7
|
+
Then the typoed result should equal to <output>
|
8
|
+
And the call to string’s typo should equal to <output>
|
9
|
+
And neither single nor double quotes are left in the string
|
10
|
+
|
11
|
+
Examples:
|
12
|
+
| input | output |
|
13
|
+
| "And God said \"∇×(∇×F) = ∇(∇·F) − ∇2F\" and there was light." | "And God said “∇×(∇×F) = ∇(∇·F) − ∇2F” and there was light." |
|
14
|
+
| "And God said \"I--heard \"Booh \"Bah\" Booh\" and \"Bam\" in heaven\" and there was light." | "And God said “I—heard ‘Booh “Bah” Booh’ and ‘Bam’ in heaven” and there was light." |
|
15
|
+
| "And God said \"I - heard \"Booh Bah Booh\" and \"Bam\" in heaven\" and there was light." | "And God said “I—heard ‘Booh Bah Booh’ and ‘Bam’ in heaven” and there was light." |
|
16
|
+
| "And God said \"Oslo coordinates are: 59°57′N 10°45′E\" and there was light." | "And God said “Oslo coordinates are: 59°57′N 10°45′E” and there was light." |
|
17
|
+
| "And God said \"That's a 6.3\" man, he sees sunsets at 10°20'30\" E.\" and there was light." | "And God said “That’s a 6.3″ man, he sees sunsets at 10°20′30″ E.” and there was light." |
|
18
|
+
| "And God said \"Foo\" , and there was light." | "And God said “Foo,” and there was light." |
|
19
|
+
| "And God said \"Baz heard 'Foos' Bar' once\" , and there was light." | "And God said “Baz heard ‘Foos’ Bar’ once,” and there was light." |
|
20
|
+
| "And God, loving ellipsis, said.... And..." | "And God, loving ellipsis, said… And…" |
|
21
|
+
|
22
|
+
Scenario Outline: Quotes and punctuation in Russian
|
23
|
+
Given the input string is <input>
|
24
|
+
When input string is processed with Typogrowl’s typography parser with lang "ru"
|
25
|
+
Then the typoed result should equal to <output>
|
26
|
+
And the call to string’s typo with lang "ru" should equal to <output>
|
27
|
+
And neither single nor double quotes are left in the string
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
| input | output |
|
31
|
+
| "И Бог сказал: \"Я - слышу \"Бум\" и \"Бам\" где-то там\" , и стало светло." | "И Бог сказал: «Я — слышу „Бум“ и „Бам“ где-то там», и стало светло." |
|
32
|
+
| "И Бог сказал: \"Я - слышу \"Бум \"и\" Бам\" где-то там\" , и стало светло." | "И Бог сказал: «Я — слышу „Бум «и» Бам“ где-то там», и стало светло." |
|
33
|
+
| "Строка со ссылкой: http://wikipedia.org (ссылка)." | "Строка со ссылкой: http://wikipedia.org (ссылка)." |
|
34
|
+
|
35
|
+
Scenario Outline: Spacing before/after punctuation
|
36
|
+
Given the input string is <input>
|
37
|
+
When input string is processed with Typogrowl’s typography parser
|
38
|
+
Then the typoed result should equal to <output>
|
39
|
+
And the call to string’s typo should equal to <output>
|
40
|
+
|
41
|
+
Examples:
|
42
|
+
| input | output |
|
43
|
+
| "It’s raining.Pity." | "It’s raining. Pity." |
|
44
|
+
| "It’s raining . Pity." | "It’s raining. Pity." |
|
45
|
+
| "It’s raining .Pity." | "It’s raining. Pity." |
|
46
|
+
| "Link http://wikipedia.org here." | "Link http://wikipedia.org here." |
|
47
|
+
| "Here is http://wikipedia.org. See?." | "Here is http://wikipedia.org. See?." |
|
48
|
+
| "Here is exclamation ellipsis!.." | "Here is exclamation ellipsis!.." |
|
49
|
+
| "Here is exclamation ellipsis! . ." | "Here is exclamation ellipsis!.." |
|
50
|
+
|
51
|
+
Scenario: Inplace string modification
|
52
|
+
Given the input string is "Foo 'Bar' Baz"
|
53
|
+
When input string is modified inplace with typo!
|
54
|
+
Then typoed result should equal to "Foo “Bar” Baz"
|
@@ -0,0 +1,132 @@
|
|
1
|
+
:quotes :
|
2
|
+
:punctuation :
|
3
|
+
:re : '(?<quote>''|"|\))\s*(?<punct>[.,!?]+)'
|
4
|
+
:default :
|
5
|
+
- '\k<punct>\k<quote>'
|
6
|
+
:ru :
|
7
|
+
- '\k<quote>\k<punct>'
|
8
|
+
|
9
|
+
# That's a 6.3" man, he sees sunsets at 10°20'30" E.
|
10
|
+
# ⇑
|
11
|
+
:inch :
|
12
|
+
:pattern : '"'
|
13
|
+
:re : '(?:\p{Space}[\.,\p{Digit}]+)(")(?=\p{Space}|\Z)'
|
14
|
+
:default :
|
15
|
+
- '″'
|
16
|
+
# That's a 6.3" man, he sees sunsets at 10°20'30" E.
|
17
|
+
# ⇑
|
18
|
+
:seconds :
|
19
|
+
:pattern : '"'
|
20
|
+
:re : '(?:\p{Space}[°''’′\p{Digit}]+)(")(?=\p{Space}|\p{Alpha}|\Z)'
|
21
|
+
:default :
|
22
|
+
- '″'
|
23
|
+
|
24
|
+
# That's a 6.3" man, he sees sunsets at 10°20'30" E.
|
25
|
+
# ⇑
|
26
|
+
:minutes :
|
27
|
+
:pattern : ''''
|
28
|
+
:re : '(\p{Space}[°\p{Digit}]+)('')(?=\p{Space}|\p{Alnum}|\Z)'
|
29
|
+
:default :
|
30
|
+
- '′'
|
31
|
+
|
32
|
+
# That's a 6.3" man, he sees sunsets at 10°20'30" E.
|
33
|
+
# ⇑
|
34
|
+
:apostrophe_pre :
|
35
|
+
:pattern : ''''
|
36
|
+
:re : '(?<=\p{Alpha})('')(?=\p{Alpha})'
|
37
|
+
:default :
|
38
|
+
- '♻'
|
39
|
+
# And God said 'Foos' game is over'.
|
40
|
+
# ⇑
|
41
|
+
:apostrophe_squeez :
|
42
|
+
:pattern : ''''
|
43
|
+
:re : '(?<=s)('')(?=\s)'
|
44
|
+
:default :
|
45
|
+
- '♻'
|
46
|
+
# And God said "∇×(∇×F) = ∇(∇·F) − ∇2F" and there was light.
|
47
|
+
# ⇑
|
48
|
+
:left :
|
49
|
+
:original : '''"'
|
50
|
+
:re : '(?<=\p{Space}|\A)(?<m>"|'')(?=\p{Graph}|\Z)'
|
51
|
+
:compliant : 'right'
|
52
|
+
# Nested quotation marks are chosen if it’s an even occurence
|
53
|
+
:default :
|
54
|
+
- '“'
|
55
|
+
- '‘'
|
56
|
+
# - '〈'
|
57
|
+
:ru :
|
58
|
+
- '«'
|
59
|
+
- '„'
|
60
|
+
:us :
|
61
|
+
- '‘'
|
62
|
+
- '“'
|
63
|
+
# And God said "∇×(∇×F) = ∇(∇·F) − ∇2F" and there was light.
|
64
|
+
# ⇑
|
65
|
+
:right :
|
66
|
+
:slave : true
|
67
|
+
:original : '''"'
|
68
|
+
:re : '(?<=\p{L}|\p{M}|\p{P})(?<m>"|'')(?=\p{Space}|\p{P}|\Z)'
|
69
|
+
:compliant : 'left'
|
70
|
+
# Nested quotation marks are chosen if it’s an odd occurence
|
71
|
+
:default :
|
72
|
+
- '”'
|
73
|
+
- '’'
|
74
|
+
# - '〉'
|
75
|
+
:ru :
|
76
|
+
- '»'
|
77
|
+
- '“'
|
78
|
+
:us :
|
79
|
+
- '’'
|
80
|
+
- '”'
|
81
|
+
|
82
|
+
# That's a 6.3" man, he sees sunsets at 10°20'30" E.
|
83
|
+
# ⇑
|
84
|
+
:apostrophe_post :
|
85
|
+
:pattern : '♻'
|
86
|
+
:re : '♻'
|
87
|
+
:default :
|
88
|
+
- '’'
|
89
|
+
|
90
|
+
:alone_single :
|
91
|
+
:re : '('')'
|
92
|
+
:alert: true
|
93
|
+
:default :
|
94
|
+
- '’'
|
95
|
+
|
96
|
+
:alone_double :
|
97
|
+
:re : '(")'
|
98
|
+
:alert: true
|
99
|
+
:default :
|
100
|
+
- '”'
|
101
|
+
|
102
|
+
:others :
|
103
|
+
:ellipsis :
|
104
|
+
:re : '\.{3,}'
|
105
|
+
:default :
|
106
|
+
- '…'
|
107
|
+
:mdash :
|
108
|
+
:re : '\s*(?<dash> - |--)\s*'
|
109
|
+
:default :
|
110
|
+
- '—'
|
111
|
+
:ru :
|
112
|
+
- ' — '
|
113
|
+
:us :
|
114
|
+
- ' – '
|
115
|
+
|
116
|
+
:punctuation :
|
117
|
+
:opening_orphan :
|
118
|
+
:re : '([(¿¡§#№]|\p{Sc})(?:\s)*'
|
119
|
+
:default :
|
120
|
+
- '\1'
|
121
|
+
:closing_orphan :
|
122
|
+
:re : '(?:\s)*([.,:;!?)])'
|
123
|
+
:default :
|
124
|
+
- '\1'
|
125
|
+
:closing_clamped :
|
126
|
+
:re : '([.,:;!?)])(?=\p{Lu})'
|
127
|
+
:default :
|
128
|
+
- '\1 '
|
129
|
+
:fixup :
|
130
|
+
:re : '([!?.]+)(?:\s*)(\.+)'
|
131
|
+
:default :
|
132
|
+
- '\1\2'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'i18n'
|
4
|
+
require_relative '../typogrowth'
|
5
|
+
|
6
|
+
class String
|
7
|
+
# Typographyes the string and returns a result
|
8
|
+
# See Typogrowth::Parser#parse
|
9
|
+
def typo lang = nil
|
10
|
+
Typogrowth::Parser.parse(self, lang ? lang : I18n.locale)
|
11
|
+
end
|
12
|
+
# Typographyes the string inplace
|
13
|
+
# See Typogrowth::Parser#parse!
|
14
|
+
def typo! lang = nil
|
15
|
+
Typogrowth::Parser.parse!(self, lang ? lang : I18n.locale)
|
16
|
+
end
|
17
|
+
end
|
data/lib/typogrowth.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'uri'
|
6
|
+
require 'base64'
|
7
|
+
require_relative 'typogrowth/version'
|
8
|
+
require_relative 'typogrowth/string'
|
9
|
+
require_relative 'utils/hash_recursive_merge'
|
10
|
+
|
11
|
+
#
|
12
|
+
# = String typographing with language support.
|
13
|
+
#
|
14
|
+
# Parses and corrects the typography in strings. It supports
|
15
|
+
# different language rules and user rules customization.
|
16
|
+
#
|
17
|
+
# The package also monkeypatches `String` class with both
|
18
|
+
# `typo` and `typo!` methods.
|
19
|
+
#
|
20
|
+
# Category:: Ruby
|
21
|
+
# Author:: Alexei Matyushkin <am@mudasobwa.ru>
|
22
|
+
# Copyright:: 2013 The Authors
|
23
|
+
# License:: MIT License
|
24
|
+
# Link:: http://rocket-science.ru/
|
25
|
+
# Source:: http://github.com/mudasobwa/typogrowth
|
26
|
+
#
|
27
|
+
module Typogrowth
|
28
|
+
# Internal exception class just to make the exception distinction possible
|
29
|
+
class MalformedRulesFile < Exception ; end
|
30
|
+
|
31
|
+
# Parses and corrects the typography in strings. It supports
|
32
|
+
# different language rules and easy user rules customization.
|
33
|
+
class Parser
|
34
|
+
attr_reader :yaml
|
35
|
+
|
36
|
+
#
|
37
|
+
# Recursively merges the initial settings with custom.
|
38
|
+
#
|
39
|
+
# To supply your own rules to processing:
|
40
|
+
#
|
41
|
+
# - create a +hash+ of additional rules in the same form as in the
|
42
|
+
# standard `typogrowth.yaml` file shipped with a project
|
43
|
+
# - merge the hash with the standard one using this function
|
44
|
+
#
|
45
|
+
# For instance, to add french rules one is to merge in the following yaml:
|
46
|
+
#
|
47
|
+
# :quotes :
|
48
|
+
# :punctuation :
|
49
|
+
# :fr : "\\k<quote>\\k<punct>"
|
50
|
+
# …
|
51
|
+
#
|
52
|
+
def self.merge custom
|
53
|
+
instance.yaml.rmerge!(custom)
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Inplace version of string typographying.
|
58
|
+
#
|
59
|
+
# Retrieves the string and changes all the typewriters quotes (doubles
|
60
|
+
# and sigles), to inches, minutes, seconds, proper quotation signs.
|
61
|
+
#
|
62
|
+
# While the input strings are e.g.
|
63
|
+
#
|
64
|
+
# And God said "Baz heard "Bar" once" , and there was light.
|
65
|
+
# That's a 6.3" man, he sees sunsets at 10°20'30" E.
|
66
|
+
#
|
67
|
+
# It will produce:
|
68
|
+
#
|
69
|
+
# And God said “Baz heard ‘Bar’ once,” and there was light.
|
70
|
+
# That’s a 6.3″ man, he sees sunsets at 10°20′30″ E.
|
71
|
+
#
|
72
|
+
# The utility also handles dashes as well.
|
73
|
+
#
|
74
|
+
# @param str [String] the string to be typographyed inplace
|
75
|
+
# @param lang the language to use rules for
|
76
|
+
#
|
77
|
+
def self.parse str, lang = :default
|
78
|
+
lang = lang.to_sym
|
79
|
+
str.split(/\R{2,}/).map { |para|
|
80
|
+
para.gsub(URI.regexp) { |m| "⚓#{Base64.encode64 m}⚓" }
|
81
|
+
instance.yaml.each { |k, values|
|
82
|
+
values.each { |k, v|
|
83
|
+
if !!v[:re]
|
84
|
+
v[lang] = v[:default] if (!v[lang] || v[lang].size.zero?)
|
85
|
+
raise MalformedRulesFile.new "Malformed rules file (no subst for #{v})" \
|
86
|
+
if !v[lang] || v[lang].size.zero?
|
87
|
+
substituted = !!v[:pattern] ?
|
88
|
+
para.gsub!(/#{v[:re]}/) { |m| m.gsub(/#{v[:pattern]}/, v[lang].first) } :
|
89
|
+
para.gsub!(/#{v[:re]}/, v[lang].first)
|
90
|
+
# logger.warn "Unsafe substitutions were made to source:\n# ⇒ #{para}"\
|
91
|
+
# if v[:alert] && substituted
|
92
|
+
if v[lang].size > 1
|
93
|
+
para.gsub!(/#{v[lang].first}/) { |m|
|
94
|
+
prev = $`
|
95
|
+
obsoletes = prev.count(v[lang].join)
|
96
|
+
compliants = values[v[:compliant].to_sym][lang] ||
|
97
|
+
values[v[:compliant].to_sym][:default]
|
98
|
+
obsoletes -= prev.count(compliants.join) \
|
99
|
+
if !!v[:compliant]
|
100
|
+
!!v[:slave] ?
|
101
|
+
obsoletes -= prev.count(v[:original]) + 1 :
|
102
|
+
obsoletes += prev.count(v[:original])
|
103
|
+
|
104
|
+
v[lang][obsoletes % v[lang].size]
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
}
|
109
|
+
}
|
110
|
+
para
|
111
|
+
}.join(%Q(
|
112
|
+
|
113
|
+
))
|
114
|
+
.gsub(/⚓(.*)⚓/m) { |m| Base64.decode64 m }
|
115
|
+
end
|
116
|
+
|
117
|
+
# Out-of-place version of `String` typographing. See #parse!
|
118
|
+
def self.parse! str, lang = :default
|
119
|
+
str.replace self.parse(str, lang)
|
120
|
+
end
|
121
|
+
private
|
122
|
+
DEFAULT_SET = 'typogrowth'
|
123
|
+
|
124
|
+
def initialize file
|
125
|
+
@yaml = YAML.load_file "#{File.dirname(__FILE__)}/config/#{file}.yaml"
|
126
|
+
@yaml.delete(:placeholder)
|
127
|
+
end
|
128
|
+
|
129
|
+
@@instance = Parser.new(DEFAULT_SET)
|
130
|
+
|
131
|
+
def self.instance
|
132
|
+
@@instance
|
133
|
+
end
|
134
|
+
|
135
|
+
private_class_method :new
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.parse str, lang = :default
|
139
|
+
Parser.parse str, lang
|
140
|
+
end
|
141
|
+
def self.parse! str, lang = :default
|
142
|
+
Parser.parse! str, lang
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#
|
2
|
+
# = Hash Recursive Merge
|
3
|
+
#
|
4
|
+
# Merges a Ruby Hash recursively, Also known as deep merge.
|
5
|
+
# Recursive version of Hash#merge and Hash#merge!.
|
6
|
+
#
|
7
|
+
# Category:: Ruby
|
8
|
+
# Package:: Hash
|
9
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
10
|
+
# Copyright:: 2007-2008 The Authors
|
11
|
+
# License:: MIT License
|
12
|
+
# Link:: http://www.simonecarletti.com/
|
13
|
+
# Source:: http://gist.github.com/gists/6391/
|
14
|
+
#
|
15
|
+
module HashRecursiveMerge
|
16
|
+
|
17
|
+
#
|
18
|
+
# Recursive version of Hash#merge!
|
19
|
+
#
|
20
|
+
# Adds the contents of +other_hash+ to +hsh+,
|
21
|
+
# merging entries in +hsh+ with duplicate keys with those from +other_hash+.
|
22
|
+
#
|
23
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
24
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
25
|
+
# it merges and returns the values from both arrays.
|
26
|
+
#
|
27
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
28
|
+
# h2 = {"b" => 254, "c" => 300, "c" => {"c1" => 16, "c3" => 94}}
|
29
|
+
# h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
30
|
+
#
|
31
|
+
# Simply using Hash#merge! would return
|
32
|
+
#
|
33
|
+
# h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
34
|
+
#
|
35
|
+
def rmerge!(other_hash)
|
36
|
+
merge!(other_hash) do |key, oldval, newval|
|
37
|
+
oldval.class == self.class ? oldval.rmerge!(newval) : newval
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Recursive version of Hash#merge
|
43
|
+
#
|
44
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
45
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
46
|
+
# it merges and returns the values from both arrays.
|
47
|
+
#
|
48
|
+
# Compared with Hash#merge, this method provides a different approch
|
49
|
+
# for merging nasted hashes.
|
50
|
+
# If the value of a given key is an Hash and both +other_hash+ abd +hsh
|
51
|
+
# includes the same key, the value is merged instead replaced with
|
52
|
+
# +other_hash+ value.
|
53
|
+
#
|
54
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
55
|
+
# h2 = {"b" => 254, "c" => 300, "c" => {"c1" => 16, "c3" => 94}}
|
56
|
+
# h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
57
|
+
#
|
58
|
+
# Simply using Hash#merge would return
|
59
|
+
#
|
60
|
+
# h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
61
|
+
#
|
62
|
+
def rmerge(other_hash)
|
63
|
+
r = {}
|
64
|
+
merge(other_hash) do |key, oldval, newval|
|
65
|
+
r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
class Hash
|
72
|
+
include HashRecursiveMerge
|
73
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
require 'typogrowth'
|
4
|
+
|
5
|
+
# Requires supporting files with custom matchers and macros, etc,
|
6
|
+
# in ./support/ and its subdirectories.
|
7
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
|
11
|
+
end
|
data/spec/test.input
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
And God said "∇×(∇×F) = ∇(∇·F) − ∇2F" and there was light.
|
2
|
+
And God said “∇×(∇×F) = ∇(∇·F) − ∇2F” and there was light.
|
3
|
+
And God said "I--heard "Booh "Bah" Booh" and "Bam" in heaven" and there was light.
|
4
|
+
And God said “I—heard ‘Booh “Bah” Booh’ and ‘Bam’ in heaven” and there was light.
|
5
|
+
And God said "I - heard "Booh Bah Booh" and "Bam" in heaven" and there was light.
|
6
|
+
And God said “I—heard ‘Booh Bah Booh’ and ‘Bam’ in heaven” and there was light.
|
7
|
+
And God said "Oslo coordinates are: 59°57′N 10°45′E" and there was light.
|
8
|
+
And God said “Oslo coordinates are: 59°57′N 10°45′E” and there was light.
|
9
|
+
And God said "That's a 6.3" man, he sees sunsets at 10°20'30" E." and there was light.
|
10
|
+
And God said “That’s a 6.3″ man, he sees sunsets at 10°20′30″ E.” and there was light.
|
11
|
+
And God said "Foo" , and there was light.
|
12
|
+
And God said “Foo,” and there was light.
|
13
|
+
And God said "Baz heard 'Foos' Bar' once" , and there was light.
|
14
|
+
And God said “Baz heard ‘Foos’ Bar’ once,” and there was light.
|
15
|
+
And God, loving ellipsis, said.... And...
|
16
|
+
And God, loving ellipsis, said… And…
|
17
|
+
|
18
|
+
An easier way to solve the problem requires understanding that regular expressions can be written as finite state machines, and vice versa.
|
19
|
+
|
20
|
+
Let's start by building the state machine which matches multiples of 3. We know that a number is a multiple of 3, if and only if the sums of the digits are a multiple of 3 (divisibility by three proof).
|
21
|
+
|
22
|
+
The state machine is going to process the input and keep track of the sum of the digits. We only need the sum modulo 3, so we'll have a pretty simple state machine with 3 states: state A (starting state), state B (we are off by 1), state C (we are off by 2).
|
23
|
+
|
24
|
+
When we are in state A, if we get a "0", "3", "6" or "9", we remain on state A. If we get a "1", "4" or "7" we move to state B. If we get a "2", "5" or "8" we move to state C.
|
data/typogrowth.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'typogrowth/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'typogrowth'
|
6
|
+
s.version = Typogrowth::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.date = '2013-10-06'
|
9
|
+
s.authors = ['Alexei Matyushkin']
|
10
|
+
s.email = 'am@mudasobwa.ru'
|
11
|
+
s.homepage = 'http://github.com/mudasobwa/typogrowth'
|
12
|
+
s.license = 'LICENSE'
|
13
|
+
s.summary = %Q{Simple library to produce typographyed texts}
|
14
|
+
s.description = %Q{Gem provides string monkeypatches to typograph strings}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
'LICENSE',
|
17
|
+
'README.md',
|
18
|
+
]
|
19
|
+
|
20
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 1.3.7')
|
21
|
+
s.rubygems_version = '1.3.7'
|
22
|
+
s.specification_version = 3
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
26
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
27
|
+
s.require_paths = ['lib']
|
28
|
+
|
29
|
+
s.add_development_dependency 'rspec'
|
30
|
+
s.add_development_dependency 'rake'
|
31
|
+
s.add_development_dependency 'yard'
|
32
|
+
s.add_development_dependency 'cucumber'
|
33
|
+
s.add_development_dependency 'yard-cucumber'
|
34
|
+
|
35
|
+
s.add_dependency 'psych'
|
36
|
+
s.add_dependency 'i18n'
|
37
|
+
end
|
38
|
+
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: typogrowth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexei Matyushkin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
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
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: cucumber
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard-cucumber
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: psych
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: i18n
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Gem provides string monkeypatches to typograph strings
|
112
|
+
email: am@mudasobwa.ru
|
113
|
+
executables:
|
114
|
+
- typo
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files:
|
117
|
+
- LICENSE
|
118
|
+
- README.md
|
119
|
+
files:
|
120
|
+
- .document
|
121
|
+
- .gitignore
|
122
|
+
- .travis.yml
|
123
|
+
- Gemfile
|
124
|
+
- LICENSE
|
125
|
+
- README.md
|
126
|
+
- Rakefile
|
127
|
+
- bin/typo
|
128
|
+
- features/step_definitions/typogrowth_steps.rb
|
129
|
+
- features/support/env.rb
|
130
|
+
- features/typogrowth.feature
|
131
|
+
- lib/config/typogrowth.yaml
|
132
|
+
- lib/typogrowth.rb
|
133
|
+
- lib/typogrowth/string.rb
|
134
|
+
- lib/typogrowth/version.rb
|
135
|
+
- lib/utils/hash_recursive_merge.rb
|
136
|
+
- spec/spec_helper.rb
|
137
|
+
- spec/test.input
|
138
|
+
- spec/typogrowth_spec.rb
|
139
|
+
- typogrowth.gemspec
|
140
|
+
homepage: http://github.com/mudasobwa/typogrowth
|
141
|
+
licenses:
|
142
|
+
- LICENSE
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ! '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 1.3.7
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 2.0.2
|
161
|
+
signing_key:
|
162
|
+
specification_version: 3
|
163
|
+
summary: Simple library to produce typographyed texts
|
164
|
+
test_files: []
|
165
|
+
has_rdoc:
|