merimee 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +85 -0
- data/Rakefile +14 -0
- data/lib/merimee.rb +20 -0
- data/lib/merimee/after_the_deadline.rb +98 -0
- data/lib/merimee/checker.rb +30 -0
- data/lib/merimee/config.rb +51 -0
- data/lib/merimee/railties/merimee_railtie.rb +7 -0
- data/lib/merimee/rspec/view_checker_helper.rb +36 -0
- data/lib/merimee/version.rb +3 -0
- data/merimee.gemspec +27 -0
- data/test/checker_test.rb +26 -0
- data/test/config_test.rb +23 -0
- metadata +84 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# merimee
|
2
|
+
|
3
|
+
> Pour parler sans ambiguïté, ce dîner à Sainte-Adresse, près du Havre, malgré les effluves embaumés de la mer, malgré les vins de très bons crus, les cuisseaux de veau et les cuissots de chevreuil prodigués par l’amphitryon, fut un vrai guêpier.
|
4
|
+
|
5
|
+
_merimee_ adds some `rspec` macros (`Test::Case` to come ... maybe) to add automatic spell checking to your tests.
|
6
|
+
|
7
|
+
## Install
|
8
|
+
|
9
|
+
```
|
10
|
+
gem 'merimee'
|
11
|
+
```
|
12
|
+
|
13
|
+
## Configuration
|
14
|
+
|
15
|
+
The config object has the following methods/arguments :
|
16
|
+
```
|
17
|
+
Merime::Checker.new do |config|
|
18
|
+
config.dict_add 'OHAI' # Ignore OHAI spelling errors. This is case sensitive for now, feel free to tell me if you feel it shouldn't be the case.
|
19
|
+
config.dict_add %w{Trealiu Chtulu} # Ignore other words, method takes any enumerable too !
|
20
|
+
config.dict_add_file 'blah.txt' # Ignore all words from blah.txt, one per line
|
21
|
+
config.language = 'en' # English by default, but AfterTheDeadline also supports French(fr), Spanish(es), German(de), Portuguese(pt)
|
22
|
+
# AtD says that you should provide a key, unique per use. You don't need to register/get it, but
|
23
|
+
# you can't have more than one request on their servers at the same time with the same key.
|
24
|
+
# Since merimee is intended for test mode, it should be fine.
|
25
|
+
config.api_key = 'blah' # See AtD documentation here : it's not needed, the gem is smart enough, especially in test mode
|
26
|
+
|
27
|
+
config.ignore_types << 'spelling' # You can ignore some types of errors.
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### Standalone use
|
34
|
+
|
35
|
+
Well, you need to initalize a `Merimee::Checker` with a `Merimee::Config`
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
checker = Merimee::Checker.new do |config|
|
39
|
+
# Configure if needed
|
40
|
+
end
|
41
|
+
# checker = Merimee::Checker.new would be equivalent here
|
42
|
+
# checker = Merimee::Checker.new(Merime::Config.new) too
|
43
|
+
|
44
|
+
checker.check('This text has one BIGE error')
|
45
|
+
=> [BIGE spelling]
|
46
|
+
```
|
47
|
+
|
48
|
+
Error objects have some interesting fields (`type`, `suggestions`, `url`, `description`), inspect them to know more.
|
49
|
+
|
50
|
+
### Rspec/rails
|
51
|
+
Just drop a `require 'merimee'` in your `spec_helper.rb`.
|
52
|
+
It gives you the following in your views :
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
describe 'splash/index' do
|
56
|
+
it_should_have_correct_spelling
|
57
|
+
end
|
58
|
+
|
59
|
+
# Which is equivalent to
|
60
|
+
describe 'splash/logout' do
|
61
|
+
it "should have a correct spelling" do
|
62
|
+
render
|
63
|
+
rendered.should have_a_correct_spelling
|
64
|
+
end
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
#### Configuration with RSpec
|
69
|
+
You can still modify your config within RSpec :
|
70
|
+
|
71
|
+
```ruby In your spec_helper.rb
|
72
|
+
require 'merimee'
|
73
|
+
|
74
|
+
RSpec.configure do |config|
|
75
|
+
config.merimee_config.dict_add 'ignoreThisWord'
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
## Known issues
|
80
|
+
|
81
|
+
See http://github.com/coutud/merimee/issues
|
82
|
+
|
83
|
+
## Thanks etc.
|
84
|
+
|
85
|
+
This gem was based on some code from https://github.com/msepcot/after_the_deadline
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs.push "lib"
|
7
|
+
t.test_files = FileList['test/*_test.rb']
|
8
|
+
t.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Open an irb session preloaded with this library"
|
12
|
+
task :console do
|
13
|
+
sh "irb -rubygems -I lib -r merimee.rb"
|
14
|
+
end
|
data/lib/merimee.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "merimee/version"
|
2
|
+
|
3
|
+
require "merimee/checker"
|
4
|
+
require "merimee/config"
|
5
|
+
require "merimee/after_the_deadline"
|
6
|
+
|
7
|
+
require 'merimee/railties/merimee_railtie.rb' if defined?(Rails)
|
8
|
+
|
9
|
+
if defined?(RSpec) && defined?(RSpec::Matchers)
|
10
|
+
require 'merimee/rspec/view_checker_helper.rb'
|
11
|
+
RSpec.configuration.add_setting :merimee_config
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.merimee_config = Merimee::Config.new
|
15
|
+
config.extend Merimee::Rspec::ViewCheckerHelper, :type => :view
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Merimee
|
20
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'crack'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
class AfterTheDeadline
|
6
|
+
BASE_URIS = {
|
7
|
+
'en' => 'http://service.afterthedeadline.com',
|
8
|
+
'fr' => 'http://fr.service.afterthedeadline.com',
|
9
|
+
'de' => 'http://de.service.afterthedeadline.com',
|
10
|
+
'pt' => 'http://pt.service.afterthedeadline.com',
|
11
|
+
'es' => 'http://es.service.afterthedeadline.com'
|
12
|
+
}
|
13
|
+
BASE_URI = 'http://service.afterthedeadline.com'
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :key, :language
|
17
|
+
# Invoke the checkDocument service with provided text.
|
18
|
+
#
|
19
|
+
# Returns list of AfterTheDeadline::Error objects.
|
20
|
+
def check(data)
|
21
|
+
results = Crack::XML.parse(perform('/checkDocument', :data => data))['results']
|
22
|
+
return [] if results.nil? # we have no errors in our data
|
23
|
+
|
24
|
+
raise "Server returned an error: #{results['message']}" if results['message']
|
25
|
+
errors = if results['error'].kind_of?(Array)
|
26
|
+
results['error'].map { |e| AfterTheDeadline::Error.new(e) }
|
27
|
+
else
|
28
|
+
[AfterTheDeadline::Error.new(results['error'])]
|
29
|
+
end
|
30
|
+
|
31
|
+
return errors
|
32
|
+
end
|
33
|
+
alias :check_document :check
|
34
|
+
|
35
|
+
# Invoke the stats service with provided text.
|
36
|
+
#
|
37
|
+
# Returns AfterTheDeadline::Metrics object.
|
38
|
+
def metrics(data)
|
39
|
+
results = Crack::XML.parse(perform('/stats', :data => data))['scores']
|
40
|
+
return if results.nil? # we have no stats about our data
|
41
|
+
AfterTheDeadline::Metrics.new results['metric']
|
42
|
+
end
|
43
|
+
alias :stats :metrics
|
44
|
+
|
45
|
+
def perform(action, params)
|
46
|
+
uri = URI.parse(BASE_URIS[@language] + action)
|
47
|
+
response = Net::HTTP.post_form uri, params.update(:key => @key)
|
48
|
+
raise "Unexpected response code from AtD service: #{response.code} #{response.message}" unless response.is_a? Net::HTTPSuccess
|
49
|
+
response.body
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private_class_method :perform
|
54
|
+
end
|
55
|
+
|
56
|
+
class AfterTheDeadline::Error
|
57
|
+
attr_reader :string, :description, :precontext, :type, :suggestions, :url
|
58
|
+
|
59
|
+
def initialize(hash)
|
60
|
+
raise "#{self.class} must be initialized with a Hash" unless hash.kind_of?(Hash)
|
61
|
+
[:string, :description, :precontext, :type, :url].each do |attribute|
|
62
|
+
self.send("#{attribute}=", hash[attribute.to_s])
|
63
|
+
end
|
64
|
+
self.suggestions = hash['suggestions'].nil? ? [] : [*hash['suggestions']['option']]
|
65
|
+
end
|
66
|
+
|
67
|
+
def info(theme = nil)
|
68
|
+
return unless self.url
|
69
|
+
uri = URI.parse self.url
|
70
|
+
uri.query = (uri.query || '') + "&theme=#{theme}"
|
71
|
+
Net::HTTP.get(uri).strip
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
"#{self.string} (#{self.description})"
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
attr_writer :string, :description, :precontext, :type, :suggestions, :url
|
80
|
+
end
|
81
|
+
|
82
|
+
class AfterTheDeadline::Metrics
|
83
|
+
attr_reader :spell, :grammer, :stats, :style
|
84
|
+
|
85
|
+
def initialize(array)
|
86
|
+
unless array.kind_of?(Array) && array.all? {|i| i.kind_of?(Hash) }
|
87
|
+
raise "#{self.class} must be initialized with an Array of Hashes"
|
88
|
+
end
|
89
|
+
|
90
|
+
self.spell, self.grammer, self.stats, self.style = {}, {}, {}, {}
|
91
|
+
array.each do |metric|
|
92
|
+
self.send(metric['type'])[metric['key']] = metric['value']
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
attr_writer :spell, :grammer, :stats, :style
|
98
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Main checker class
|
2
|
+
# Heavily inspired by the old AfterTheDeadline github repo
|
3
|
+
module Merimee
|
4
|
+
class Checker
|
5
|
+
attr_reader :config
|
6
|
+
def initialize(config = nil)
|
7
|
+
@config = config || Config.new
|
8
|
+
yield @config if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def check(text, options = {})
|
12
|
+
AfterTheDeadline.language = options[:language] || @config.language
|
13
|
+
AfterTheDeadline.key = @config.api_key
|
14
|
+
errors = AfterTheDeadline.check(text)
|
15
|
+
|
16
|
+
# Remove any error types we don't care about
|
17
|
+
errors.reject! { |e| @config.ignore_types.include?(e.description) }
|
18
|
+
|
19
|
+
# Remove spelling errors from our custom dictionary
|
20
|
+
# Also remove stuff that is obviously not a word (AtD seems to have issues sometime)
|
21
|
+
errors.reject! do |e|
|
22
|
+
e.type == 'spelling' &&
|
23
|
+
@config.dictionary.include?(e.string)
|
24
|
+
end
|
25
|
+
errors
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Merimee
|
4
|
+
DEFAULT_IGNORE_TYPES = ['Bias Language', 'Cliches', 'Complex Expression', 'Diacritical Marks', 'Double Negatives', 'Hidden Verbs', 'Jargon Language', 'Passive voice', 'Phrases to Avoid', 'Redundant Expression']
|
5
|
+
DEFAULT_LANGUAGE = 'en'
|
6
|
+
|
7
|
+
class Config
|
8
|
+
attr_reader :dictionary
|
9
|
+
attr_accessor :ignore_types
|
10
|
+
|
11
|
+
# This is required by the service for caching purposes,
|
12
|
+
# but no registration is needed (api_key should just be unique for
|
13
|
+
# your app, only one request can happen per key at any moment).
|
14
|
+
# There shouldn't be any need for this, but still
|
15
|
+
attr_accessor :api_key
|
16
|
+
# The language to use for this check
|
17
|
+
# This is used to select the proper AtD server, unless overriden with the
|
18
|
+
# :language option
|
19
|
+
attr_accessor :language
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@dictionary = []
|
23
|
+
@ignore_types = Merimee::DEFAULT_IGNORE_TYPES
|
24
|
+
@api_key = Config.generate_api_key
|
25
|
+
@language = Merimee::DEFAULT_LANGUAGE
|
26
|
+
end
|
27
|
+
|
28
|
+
def dict_add(word)
|
29
|
+
if word.kind_of?(String)
|
30
|
+
@dictionary << word
|
31
|
+
elsif word.kind_of?(Enumerable)
|
32
|
+
@dictionary.concat word
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def dict_add_file(file)
|
37
|
+
File.open(file) {|f| dict_add(f.readlines.map(&:strip)) }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
# We just use the path to this file, and the current hostname
|
42
|
+
# If you
|
43
|
+
# This is probably not totally unique
|
44
|
+
# We use the mac address and this file path, should be unique enough
|
45
|
+
def self.generate_api_key
|
46
|
+
data = [File.expand_path(__FILE__)]
|
47
|
+
data << Mac.addr if defined?(Mac)
|
48
|
+
Digest::SHA1.hexdigest(data.join)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Merimee
|
2
|
+
module Rspec
|
3
|
+
module ViewCheckerHelper
|
4
|
+
RSpec::Matchers.define :have_a_correct_spelling do
|
5
|
+
match do |rendered|
|
6
|
+
checker = Merimee::Checker.new(::RSpec.configuration.merimee_config)
|
7
|
+
|
8
|
+
if rendered
|
9
|
+
@errors = checker.check(rendered)
|
10
|
+
@errors.empty?
|
11
|
+
else
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
failure_message_for_should do |view|
|
16
|
+
errs = []
|
17
|
+
@errors.each do |err|
|
18
|
+
message = "[#{err.type}] #{err.string}"
|
19
|
+
if err.suggestions
|
20
|
+
message << " (suggested: #{err.suggestions.join(', ')})"
|
21
|
+
end
|
22
|
+
errs << message
|
23
|
+
end
|
24
|
+
errs.join("\n")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def it_should_have_a_correct_spelling
|
29
|
+
it "should have a correct spelling" do
|
30
|
+
render
|
31
|
+
rendered.should have_a_correct_spelling
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/merimee.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "merimee/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "merimee"
|
7
|
+
s.version = Merimee::VERSION
|
8
|
+
s.authors = ["coutud"]
|
9
|
+
s.email = ["wam@atwam.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Add spell checking to your views tests/specs}
|
12
|
+
s.description = %q{Automatically submit your rendered views to AfterTheDeadline free spell check service, and make sure you don't have any errors in your views !}
|
13
|
+
|
14
|
+
s.rubyforge_project = "merimee"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_runtime_dependency 'crack'
|
23
|
+
|
24
|
+
s.add_development_dependency 'minitest'
|
25
|
+
# s.add_development_dependency "rspec"
|
26
|
+
# s.add_runtime_dependency "rest-client"
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'merimee'
|
3
|
+
|
4
|
+
require 'digest/sha1'
|
5
|
+
|
6
|
+
class CheckerTest < MiniTest::Unit::TestCase
|
7
|
+
ATD_SLEEP = 0.7
|
8
|
+
def setup
|
9
|
+
@checker = Merimee::Checker.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_check_perfect_text
|
13
|
+
sleep ATD_SLEEP # Make sure we sleep a bit, AtD returns 503 if requests are too close
|
14
|
+
errors = @checker.check("Perfect text")
|
15
|
+
assert_empty errors
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_text_with_errors
|
19
|
+
erroneous_text = "Wrong text garanteed with errors"
|
20
|
+
sleep ATD_SLEEP # Make sure we sleep a bit, AtD returns 503 if requests are too close
|
21
|
+
errors = @checker.check(erroneous_text)
|
22
|
+
|
23
|
+
refute_empty errors
|
24
|
+
assert_equal "garanteed", errors.first.string
|
25
|
+
end
|
26
|
+
end
|
data/test/config_test.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'merimee'
|
3
|
+
|
4
|
+
class ConfigTest < MiniTest::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@config = Merimee::Config.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_ignore_type_not_empty_by_default
|
10
|
+
refute_empty @config.ignore_types
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_add_dict_with_word
|
14
|
+
@config.dict_add "OHAI"
|
15
|
+
assert_includes @config.dictionary, "OHAI"
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_add_dict_with_array
|
19
|
+
@config.dict_add "OHAI"
|
20
|
+
@config.dict_add ["Foo", "Bar"]
|
21
|
+
assert_equal ["OHAI", "Foo", "Bar"], @config.dictionary
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: merimee
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- coutud
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: crack
|
16
|
+
requirement: &70150877255760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70150877255760
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: minitest
|
27
|
+
requirement: &70150877255200 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70150877255200
|
36
|
+
description: Automatically submit your rendered views to AfterTheDeadline free spell
|
37
|
+
check service, and make sure you don't have any errors in your views !
|
38
|
+
email:
|
39
|
+
- wam@atwam.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- lib/merimee.rb
|
49
|
+
- lib/merimee/after_the_deadline.rb
|
50
|
+
- lib/merimee/checker.rb
|
51
|
+
- lib/merimee/config.rb
|
52
|
+
- lib/merimee/railties/merimee_railtie.rb
|
53
|
+
- lib/merimee/rspec/view_checker_helper.rb
|
54
|
+
- lib/merimee/version.rb
|
55
|
+
- merimee.gemspec
|
56
|
+
- test/checker_test.rb
|
57
|
+
- test/config_test.rb
|
58
|
+
homepage: ''
|
59
|
+
licenses: []
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project: merimee
|
78
|
+
rubygems_version: 1.8.10
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Add spell checking to your views tests/specs
|
82
|
+
test_files:
|
83
|
+
- test/checker_test.rb
|
84
|
+
- test/config_test.rb
|