smartdict-core 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +5 -2
- data/VERSION +1 -1
- data/bin/smartdict +3 -7
- data/bin/smartdict-populator +6 -3
- data/config/default_config.yml +4 -0
- data/lib/smartdict/commands/list_command.rb +2 -2
- data/lib/smartdict/commands/translate_command.rb +11 -4
- data/lib/smartdict/commands/version_command.rb +29 -0
- data/lib/smartdict/commands.rb +1 -0
- data/lib/smartdict/core/command_manager.rb +3 -0
- data/lib/smartdict/core/driver_manager.rb +1 -0
- data/lib/smartdict/drivers/abstract_driver.rb +4 -3
- data/lib/smartdict/drivers/lingvo_yandex_driver.rb +146 -0
- data/lib/smartdict/drivers.rb +1 -0
- data/lib/smartdict/errors.rb +13 -1
- data/lib/smartdict/info.rb +14 -0
- data/lib/smartdict/log.rb +108 -0
- data/lib/smartdict/models/translation.rb +13 -12
- data/lib/smartdict/models/word.rb +0 -1
- data/lib/smartdict/storage/seeder.rb +6 -3
- data/lib/smartdict/storage/seeds/drivers.csv +1 -0
- data/lib/smartdict/storage/seeds/word_classes.csv +3 -1
- data/lib/smartdict/translator/base.rb +35 -0
- data/lib/smartdict/translator/driver_configuration.rb +19 -0
- data/lib/smartdict/translator/language_detector.rb +72 -0
- data/lib/smartdict/translator.rb +18 -23
- data/lib/smartdict.rb +6 -1
- metadata +27 -8
- data/lib/smartdict/list_builder.rb +0 -51
- data/lib/smartdict/runner.rb +0 -5
data/README.markdown
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# smartdict-core [![Build Status](https://secure.travis-ci.org/smartdict/smartdict-core.png)](http://travis-ci.org/smartdict/smartdict-core)
|
2
2
|
|
3
|
-
|
3
|
+
By [Sergey Potapov](https://github.com/greyblake)
|
4
|
+
|
5
|
+
|
6
|
+
Core of [Smartdict dictionary](https://github.com/smartdict/smartdict).
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/bin/smartdict
CHANGED
@@ -4,17 +4,13 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
|
4
4
|
require 'rubygems'
|
5
5
|
|
6
6
|
require 'smartdict'
|
7
|
-
require 'smartdict/runner'
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
Smartdict.env = :user
|
8
|
+
env = ENV["SMARTDICT_ENV"] ? ENV["SMARTDICT_ENV"].to_sym : :user
|
9
|
+
Smartdict.env = env
|
13
10
|
|
14
11
|
args = ARGV.clone
|
15
12
|
|
16
13
|
Smartdict.load_plugins
|
17
14
|
|
18
|
-
# TODO :move Smartdict.run to Runner.run
|
19
15
|
Smartdict.run
|
20
|
-
Smartdict::
|
16
|
+
Smartdict::Core::CommandManager.run(args)
|
data/bin/smartdict-populator
CHANGED
@@ -15,11 +15,14 @@ Smartdict.load_plugins
|
|
15
15
|
# TODO :mode Smartdict.run to Runner.run
|
16
16
|
Smartdict.run
|
17
17
|
|
18
|
+
$translator = Smartdict::Translator.new
|
18
19
|
|
19
20
|
|
20
21
|
def translate(word)
|
21
|
-
|
22
|
+
$translator.translate(word, :log => true)
|
22
23
|
rescue Smartdict::TranslationNotFound
|
24
|
+
rescue NoMethodError => err
|
25
|
+
puts "Trouble word: #{word}"
|
23
26
|
end
|
24
27
|
|
25
28
|
def translate_words(words, from_lang, to_lang)
|
@@ -37,8 +40,8 @@ to_lang = Smartdict::Models::Language.first(:code => 'ru')
|
|
37
40
|
|
38
41
|
|
39
42
|
while(true)
|
40
|
-
|
41
|
-
|
43
|
+
$translator.default_opts[:from_lang] = from_lang.code
|
44
|
+
$translator.default_opts[:to_lang] = to_lang.code
|
42
45
|
|
43
46
|
words = Smartdict::Models::Word.all(:language_id => from_lang.id).map(&:name)
|
44
47
|
translate_words(words, from_lang, to_lang)
|
data/config/default_config.yml
CHANGED
@@ -24,14 +24,14 @@ module Smartdict::Commands
|
|
24
24
|
:driver => nil
|
25
25
|
|
26
26
|
def execute
|
27
|
-
|
27
|
+
fetch_opts = {
|
28
28
|
:since => @options[:since],
|
29
29
|
:till => @options[:till],
|
30
30
|
:from_lang => @options[:from],
|
31
31
|
:to_lang => @options[:to],
|
32
32
|
:driver => @options[:driver]
|
33
33
|
}
|
34
|
-
translations = Smartdict::
|
34
|
+
translations = Smartdict::Log.fetch(fetch_opts)
|
35
35
|
puts format.format_list(translations)
|
36
36
|
end
|
37
37
|
|
@@ -16,12 +16,19 @@ module Smartdict::Commands
|
|
16
16
|
|
17
17
|
options :from => lambda { configatron.default.from_lang },
|
18
18
|
:to => lambda { configatron.default.to_lang },
|
19
|
-
:format => lambda { configatron.default.format }
|
19
|
+
:format => lambda { configatron.default.format },
|
20
|
+
:driver => nil
|
20
21
|
|
21
22
|
def execute
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
translator_opts = {
|
24
|
+
:from_lang => @options[:from],
|
25
|
+
:to_lang => @options[:to],
|
26
|
+
:log => true
|
27
|
+
}
|
28
|
+
translator_opts[:driver] = @options[:driver] if @options[:driver]
|
29
|
+
|
30
|
+
translator = Smartdict::Translator.new(translator_opts)
|
31
|
+
translation = translator.translate(@arguments[:word])
|
25
32
|
puts format.format_translation(translation)
|
26
33
|
end
|
27
34
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Smartdict::Commands
|
2
|
+
# Displays information about the program.
|
3
|
+
# It's executed when user runs one of:
|
4
|
+
# * smartdict -v
|
5
|
+
# * smartdict --version
|
6
|
+
# * smartdict version
|
7
|
+
class VersionCommand < AbstractCommand
|
8
|
+
include Smartdict::Core
|
9
|
+
|
10
|
+
set_name "version"
|
11
|
+
set_summary "Displays information about Smartdict"
|
12
|
+
set_description "Displays information about Smartdict"
|
13
|
+
set_syntax prog_name
|
14
|
+
|
15
|
+
set_usage <<-SYNTAX
|
16
|
+
smartdict --version
|
17
|
+
smartdict -v
|
18
|
+
#{prog_name}
|
19
|
+
SYNTAX
|
20
|
+
|
21
|
+
# :nodoc:
|
22
|
+
def execute
|
23
|
+
info = Smartdict.info
|
24
|
+
puts "Smartdict core v#{info.version}\n" \
|
25
|
+
"Author: #{info.author} (#{info.email})\n" \
|
26
|
+
"URL: #{info.url}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/smartdict/commands.rb
CHANGED
@@ -3,6 +3,7 @@ class Smartdict::Core::CommandManager
|
|
3
3
|
include Smartdict::Commands
|
4
4
|
|
5
5
|
register 'help' , HelpCommand
|
6
|
+
register 'version' , VersionCommand
|
6
7
|
register 'translate', TranslateCommand
|
7
8
|
register 'list' , ListCommand
|
8
9
|
|
@@ -12,6 +13,8 @@ class Smartdict::Core::CommandManager
|
|
12
13
|
case first_arg
|
13
14
|
when nil, '-h', '--help', 'help'
|
14
15
|
run_command :help, args
|
16
|
+
when nil, '-v', '--version', 'version'
|
17
|
+
run_command :version, args
|
15
18
|
else
|
16
19
|
run_command(first_arg, args)
|
17
20
|
end
|
@@ -27,7 +27,7 @@ class Smartdict::Drivers::AbstractDriver
|
|
27
27
|
attr_accessor :translated, :transcription
|
28
28
|
|
29
29
|
# Is used to identify a driver
|
30
|
-
|
30
|
+
class_attribute :name
|
31
31
|
|
32
32
|
def self.translate(*args)
|
33
33
|
self.new(*args).build_translation
|
@@ -40,9 +40,10 @@ class Smartdict::Drivers::AbstractDriver
|
|
40
40
|
|
41
41
|
def initialize(word, from_lang, to_lang)
|
42
42
|
@word = word
|
43
|
-
@from_lang = from_lang
|
44
|
-
@to_lang = to_lang
|
43
|
+
@from_lang = from_lang.to_s
|
44
|
+
@to_lang = to_lang.to_s
|
45
45
|
translate
|
46
|
+
raise Smartdict::TranslationNotFound if(translated.nil? || translated.empty?)
|
46
47
|
end
|
47
48
|
|
48
49
|
def build_translation
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module Smartdict::Drivers
|
7
|
+
# The translation driver for Google Translate service.
|
8
|
+
#
|
9
|
+
# DISCLAIMER:
|
10
|
+
# It's was written when I had one hand broken. Refactoring costs a lot of
|
11
|
+
# movements so that I've left it as it was. I'm gonna refactor it soon.
|
12
|
+
# -- Sergey Potapov
|
13
|
+
#
|
14
|
+
# TODO:
|
15
|
+
# * Refactor
|
16
|
+
class LingvoYandexDriver < AbstractDriver
|
17
|
+
|
18
|
+
# Pretend being Firefox :)
|
19
|
+
USER_AGENT = "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.1.16) Gecko/20110429 Iceweasel/3.5.16 (like Firefox/3.5.1623123)"
|
20
|
+
|
21
|
+
# Host of Lingvo service.
|
22
|
+
HOST = "lingvo.yandex.ru"
|
23
|
+
|
24
|
+
# Mapping for word classes. Default is "other"
|
25
|
+
WORD_CLASSES = {
|
26
|
+
"имя существительное" => "noun",
|
27
|
+
"имя прилагательное" => "adjective",
|
28
|
+
"глагол" => "verb",
|
29
|
+
"наречие" => "adverb",
|
30
|
+
"предлог" => "preposition",
|
31
|
+
"имя числительное" => "numeral",
|
32
|
+
"междометие (часть речи)" => "interjection",
|
33
|
+
"сокращение" => "abbreviation",
|
34
|
+
"местоимение" => "pronoun",
|
35
|
+
"союз (часть речи)" => "conjunction"
|
36
|
+
}.tap{ |hash| hash.default = "other" }
|
37
|
+
|
38
|
+
set_name "lingvo_yandex"
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
# TODO: refactor
|
43
|
+
def translate
|
44
|
+
doc = Nokogiri::HTML(get_response)
|
45
|
+
|
46
|
+
if main_div = doc.css("div.b-translate > div.b-translate__value > ul > li#I").first
|
47
|
+
translate__value = main_div
|
48
|
+
else
|
49
|
+
main_div = doc.css("div.b-translate").first
|
50
|
+
translate__value = main_div.css("div.b-translate__value")
|
51
|
+
end
|
52
|
+
|
53
|
+
# Fetch transcription
|
54
|
+
self.transcription = main_div.xpath('(.//span[@class="b-translate__tr"])[1]').try(:text)
|
55
|
+
|
56
|
+
self.translated = {}
|
57
|
+
|
58
|
+
if translate__value.xpath("./i/acronym").any?
|
59
|
+
grep_meanings(translate__value)
|
60
|
+
else
|
61
|
+
translate__value.xpath("./ul/li").each do |li|
|
62
|
+
grep_meanings(li)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO: refactor
|
68
|
+
def grep_meanings(html_element)
|
69
|
+
acronym = html_element.css("acronym").first
|
70
|
+
return unless acronym
|
71
|
+
|
72
|
+
ru_word_class = acronym["title"]
|
73
|
+
word_class = WORD_CLASSES[ru_word_class]
|
74
|
+
translations = []
|
75
|
+
|
76
|
+
html_element.css("ul > li").each do |tr|
|
77
|
+
# a text line with translations separated by commas
|
78
|
+
line = ""
|
79
|
+
|
80
|
+
# use strong tag as an anchor
|
81
|
+
strong = tr.css("strong").first
|
82
|
+
if strong && strong.text =~ /\d+|[а-я]+\)/
|
83
|
+
node = strong
|
84
|
+
while(node = node.next_sibling)
|
85
|
+
if node.text? || node.name == "a"
|
86
|
+
text = node.text
|
87
|
+
line << text unless text =~ /\(|\)/
|
88
|
+
elsif ["em", "acronym"].include? node.name
|
89
|
+
next
|
90
|
+
else
|
91
|
+
break
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
translations += words_from_line(line)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# sometimes there is only one meaning
|
100
|
+
if translations.empty?
|
101
|
+
if a_tag = html_element.css("span > a").first
|
102
|
+
line = a_tag.text
|
103
|
+
elsif span = html_element.css("span").first
|
104
|
+
line = span.text
|
105
|
+
elsif i_tag = html_element.xpath("i[2]")
|
106
|
+
line = i_tag.text
|
107
|
+
else
|
108
|
+
return nil
|
109
|
+
end
|
110
|
+
translations = words_from_line(line)
|
111
|
+
end
|
112
|
+
|
113
|
+
self.translated[word_class] = translations.uniq
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_response
|
117
|
+
http = Net::HTTP.new(HOST, 80)
|
118
|
+
request = Net::HTTP::Get.new(http_path, { "User-Agent" => USER_AGENT })
|
119
|
+
http.request(request).read_body
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [String] http path for request to translate word.
|
123
|
+
def http_path
|
124
|
+
phrase = case [from_lang, to_lang]
|
125
|
+
when ["en", "ru"] then "с английского"
|
126
|
+
# ru -> en does not seem to be good and it's not trivial to parse
|
127
|
+
# when ["ru", "en"] then "по-английски"
|
128
|
+
else raise Smartdict::TranslationNotFound
|
129
|
+
end
|
130
|
+
|
131
|
+
"/#{escape(word)}/#{escape(phrase)}/"
|
132
|
+
end
|
133
|
+
|
134
|
+
def escape(str)
|
135
|
+
CGI.escape(str)
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def words_from_line(line)
|
142
|
+
line.split(/,|;/).map(&:strip).reject(&:empty?)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
data/lib/smartdict/drivers.rb
CHANGED
data/lib/smartdict/errors.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Smartdict
|
2
2
|
|
3
|
-
class Error < ::
|
3
|
+
class Error < ::StandardError; end
|
4
|
+
|
4
5
|
|
5
6
|
class TranslationNotFound < Error
|
6
7
|
def initialize(msg = "Translation is not found")
|
@@ -8,4 +9,15 @@ module Smartdict
|
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
12
|
+
|
13
|
+
class MissingOption < Error
|
14
|
+
def initialize(option)
|
15
|
+
@option = option
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
"Translator option is missing: `#{@option}`"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
11
23
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Smartdict
|
4
|
+
class Info < Struct.new(:version, :author, :email, :url)
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.version = Smartdict::VERSION
|
9
|
+
self.author = "Sergey Potapov"
|
10
|
+
self.email = "open.smartdict@gmail.com"
|
11
|
+
self.url = "http://smartdict.net"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
class Smartdict::Log
|
2
|
+
include Smartdict::Models
|
3
|
+
|
4
|
+
# Options:
|
5
|
+
# * +:since+
|
6
|
+
# * +:till+
|
7
|
+
# * +:from_lang+
|
8
|
+
# * +:to_lang+
|
9
|
+
# * +:driver+
|
10
|
+
# * +:limit+
|
11
|
+
# * +:order_desc+
|
12
|
+
# * +:unique+
|
13
|
+
#
|
14
|
+
# @return [Array] array of {Smartdict::Translation}.
|
15
|
+
def self.fetch(options = {})
|
16
|
+
new(options).send(:fetch)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def initialize(options)
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch
|
27
|
+
fetch_translations.map(&:to_struct)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
# Unfortunately it's not possible to build necessary query with DataMapper
|
33
|
+
# TODO: Refactor in QueryBuilder class.
|
34
|
+
def fetch_translations
|
35
|
+
adapter = Translation.repository.adapter
|
36
|
+
|
37
|
+
translations_table = Translation.storage_name
|
38
|
+
queries_table = TranslationQuery.storage_name
|
39
|
+
|
40
|
+
sql = "SELECT #{'DISTINCT' if @options[:unique]} #{translations_table}.id \n" \
|
41
|
+
"FROM #{translations_table} \n" \
|
42
|
+
"INNER JOIN #{queries_table} ON \n" \
|
43
|
+
" #{queries_table}.translation_id == #{translations_table}.id"
|
44
|
+
|
45
|
+
if [:till, :since, :from_lang, :to_lang, :driver].any? {|opt| @options[opt] }
|
46
|
+
sql << " WHERE "
|
47
|
+
|
48
|
+
if @options[:till]
|
49
|
+
till = @options[:till].is_a?(String) ? Date.parse(@options[:till]) : @options[:till]
|
50
|
+
sql << " #{queries_table}.created_at < \"#{till.to_s(:db)}\" "
|
51
|
+
where = true
|
52
|
+
end
|
53
|
+
|
54
|
+
if @options[:since]
|
55
|
+
sql << " AND " if where
|
56
|
+
since = @options[:since].is_a?(String) ? Date.parse(@options[:since]) : @options[:since]
|
57
|
+
sql << " #{queries_table}.created_at > \"#{since.to_s(:db)}\" "
|
58
|
+
where = true
|
59
|
+
end
|
60
|
+
|
61
|
+
if from_lang
|
62
|
+
sql << " AND " if where
|
63
|
+
sql << " #{translations_table}.from_lang_id = #{from_lang.id} "
|
64
|
+
where = true
|
65
|
+
end
|
66
|
+
|
67
|
+
if to_lang
|
68
|
+
sql << " AND " if where
|
69
|
+
sql << " #{translations_table}.to_lang_id = #{to_lang.id} "
|
70
|
+
where = true
|
71
|
+
end
|
72
|
+
|
73
|
+
if driver
|
74
|
+
sql << " AND " if where
|
75
|
+
sql << " #{translations_table}.driver_id = #{driver.id} "
|
76
|
+
where = true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if @options[:order_desc]
|
81
|
+
sql << " ORDER BY #{queries_table}.created_at DESC "
|
82
|
+
else
|
83
|
+
sql << " ORDER BY #{queries_table}.created_at "
|
84
|
+
end
|
85
|
+
|
86
|
+
if @options[:limit]
|
87
|
+
sql << " LIMIT #{@options[:limit]} "
|
88
|
+
end
|
89
|
+
|
90
|
+
ids = adapter.select(sql)
|
91
|
+
trs = Translation.all(:id => ids)
|
92
|
+
# Sort manually, since database doesn't guaranty required order
|
93
|
+
trs.sort! { |a, b| ids.index(a.id) <=> ids.index(b.id) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def from_lang
|
97
|
+
Language[@options[:from_lang]]
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_lang
|
101
|
+
Language[@options[:to_lang]]
|
102
|
+
end
|
103
|
+
|
104
|
+
def driver
|
105
|
+
Driver[@options[:driver]]
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
@@ -2,11 +2,12 @@ class Smartdict::Models::Translation
|
|
2
2
|
include DataMapper::Resource
|
3
3
|
include Smartdict::Models
|
4
4
|
|
5
|
-
property :id
|
6
|
-
property :word_id
|
7
|
-
property :from_lang_id, Integer, :unique_index => :index_translation
|
8
|
-
property :to_lang_id
|
9
|
-
property :driver_id
|
5
|
+
property :id , Serial
|
6
|
+
property :word_id , Integer, :unique_index => :index_translation
|
7
|
+
property :from_lang_id , Integer, :unique_index => :index_translation
|
8
|
+
property :to_lang_id , Integer, :unique_index => :index_translation
|
9
|
+
property :driver_id , Integer, :unique_index => :index_translation
|
10
|
+
property :transcription, String
|
10
11
|
|
11
12
|
belongs_to :word
|
12
13
|
belongs_to :driver
|
@@ -28,14 +29,14 @@ class Smartdict::Models::Translation
|
|
28
29
|
driver = Driver[struct.driver]
|
29
30
|
|
30
31
|
word = Word.first_or_create(:name => struct.word, :language_id => from_lang.id)
|
31
|
-
word.transcription = struct.transcription if word.transcription.blank?
|
32
32
|
word.save!
|
33
33
|
|
34
34
|
translation = self.create(
|
35
|
-
:word
|
36
|
-
:driver
|
37
|
-
:from_lang_id
|
38
|
-
:to_lang_id
|
35
|
+
:word => word,
|
36
|
+
:driver => driver,
|
37
|
+
:from_lang_id => from_lang.id,
|
38
|
+
:to_lang_id => to_lang.id,
|
39
|
+
:transcription => struct.transcription
|
39
40
|
)
|
40
41
|
|
41
42
|
struct.translated.each do |word_class_name, meanings|
|
@@ -59,7 +60,7 @@ class Smartdict::Models::Translation
|
|
59
60
|
end
|
60
61
|
|
61
62
|
|
62
|
-
# TODO: it's a hack
|
63
|
+
# TODO: it's a hack which necessary because of weird DM bugs.
|
63
64
|
def initialize(*args)
|
64
65
|
self.class.finalize
|
65
66
|
self.word_class_id = 1
|
@@ -69,7 +70,7 @@ class Smartdict::Models::Translation
|
|
69
70
|
def to_struct
|
70
71
|
struct = Smartdict::Translation.new(
|
71
72
|
:word => word.name,
|
72
|
-
:transcription =>
|
73
|
+
:transcription => self.transcription,
|
73
74
|
:from_lang => from_lang.code,
|
74
75
|
:to_lang => to_lang.code,
|
75
76
|
:driver => driver.name
|
@@ -19,8 +19,11 @@ module Smartdict::Storage::Seeder
|
|
19
19
|
|
20
20
|
def seed_from(csv_file)
|
21
21
|
model = csv_file_to_model(csv_file)
|
22
|
-
CSV.
|
23
|
-
|
22
|
+
csv = CSV.open(csv_file, 'r')
|
23
|
+
headers = csv.shift
|
24
|
+
while(row = csv.shift and not row.empty?)
|
25
|
+
attrs = Hash[headers.zip(row)]
|
26
|
+
model.create(attrs)
|
24
27
|
end
|
25
28
|
end
|
26
29
|
|
@@ -29,4 +32,4 @@ module Smartdict::Storage::Seeder
|
|
29
32
|
model_name = plural_name.classify
|
30
33
|
Smartdict::Models.const_get(model_name)
|
31
34
|
end
|
32
|
-
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# The very basic translator middleware.
|
2
|
+
module Smartdict
|
3
|
+
class Translator::Base
|
4
|
+
# Just to make the interface compatible
|
5
|
+
def initialize(translator = nil)
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(word, opts)
|
9
|
+
validate_opts!(opts)
|
10
|
+
driver = Smartdict::Core::DriverManager.find(opts[:driver])
|
11
|
+
|
12
|
+
translation_model = Models::Translation.find(word, opts[:from_lang], opts[:to_lang], opts[:driver])
|
13
|
+
unless translation_model
|
14
|
+
translation = driver.translate(word, opts[:from_lang], opts[:to_lang])
|
15
|
+
translation_model = Models::Translation.create_from_struct(translation)
|
16
|
+
end
|
17
|
+
log_query(translation_model) if opts[:log]
|
18
|
+
translation_model.to_struct
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def validate_opts!(opts)
|
25
|
+
required_opts = [:from_lang, :to_lang, :driver, :log]
|
26
|
+
required_opts.each do |opt|
|
27
|
+
raise(MissingOption.new(opt)) if opts[opt].nil?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_query(translation)
|
32
|
+
Models::TranslationQuery.create(:translation => translation)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Middleware
|
2
|
+
class Smartdict::Translator::DriverConfiguration
|
3
|
+
def initialize(translator)
|
4
|
+
@translator = translator
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(word, opts)
|
8
|
+
unless opts[:driver]
|
9
|
+
opts[:driver] = define_driver(opts[:from_lang], opts[:to_lang])
|
10
|
+
end
|
11
|
+
@translator.call(word, opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def define_driver(from_lang, to_lang)
|
15
|
+
key = "#{from_lang}-#{to_lang}"
|
16
|
+
conf = configatron.drivers
|
17
|
+
conf.retrieve(key) || conf.retrieve(:default) || raise(Smartdict::Error.new("No default driver"))
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# It's a translator middleware which tries to identify a language of the
|
4
|
+
# passed word basing on :from_lang and :to_lang options. If it's possible to
|
5
|
+
# explicitly identify a language and :from_lang and :to_lang options are
|
6
|
+
# required to be exchanged it exchanges them.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# detector = LanguageDetector.new(translator)
|
10
|
+
# detector.call("groß", :from_lang => :en, :to_lang => :de)
|
11
|
+
# # It delegates call to translator like this:
|
12
|
+
# # translator.call("groß", :from_lang => :de, :to_lang => :en)
|
13
|
+
# # Because in English there is no character "ß" but it exists in German.
|
14
|
+
#
|
15
|
+
# NOTE: It doesn't support Ruby 1.8
|
16
|
+
class Smartdict::Translator::LanguageDetector
|
17
|
+
|
18
|
+
CHARS = {
|
19
|
+
:ru => %w(А а Б б В в Г г Д д Е е Ё ё Ж ж З з И и Й й К к Л л М м Н н О о П п Р р С с Т т У у Ф ф Х х Ц ц Ч ч Ш ш Щ щ Ъ ъ Ы ы Ь ь Э э Ю ю Я я),
|
20
|
+
:en => %w(a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O p P q Q r R s S t T u U v V w W x X y Y z Z),
|
21
|
+
:uk => %w(А а Б б В в Г г Ґ ґ Д д Е е Є є Ж ж З з И и І і Ї ї Й й К к Л л М м Н н О о П п Р р С с Т т У у Ф ф Х х Ц ц Ч ч Ш ш Щ щ Ь ь Ю ю Я я '),
|
22
|
+
:de => %w(a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O p P q Q r R s S t T u U v V w W x X y Y z Z Ä ä Ö ö Ü ü ß),
|
23
|
+
|
24
|
+
# TODO: Add vowels with stresses
|
25
|
+
:es => %w(A a B b С с D d E e F f G g H h I i J j K k L l M m N n Ň ñ O o P p Q q R r S s T t U u V v W w X x Y y Z z)
|
26
|
+
}
|
27
|
+
|
28
|
+
def initialize(translator)
|
29
|
+
@translator = translator
|
30
|
+
|
31
|
+
@matchers = {}
|
32
|
+
CHARS.each do |lang, chars|
|
33
|
+
@matchers[lang] = Matcher.new(chars)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(word, opts)
|
38
|
+
if exchange?(word, opts[:from_lang], opts[:to_lang]) && !ruby18?
|
39
|
+
opts[:to_lang], opts[:from_lang] = opts[:from_lang], opts[:to_lang]
|
40
|
+
end
|
41
|
+
@translator.call(word, opts)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def exchange?(word, from_lang, to_lang)
|
48
|
+
from_matcher = @matchers[from_lang.to_sym]
|
49
|
+
to_matcher = @matchers[to_lang.to_sym]
|
50
|
+
return false unless [from_matcher, to_matcher].all?
|
51
|
+
to_matcher.match?(word) && !from_matcher.match?(word)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ruby18?
|
55
|
+
RUBY_VERSION =~ /^1\.8/
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
class Matcher
|
61
|
+
def initialize(chars)
|
62
|
+
@chars = chars + [" ", "-"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def match?(word)
|
66
|
+
word.each_char do |char|
|
67
|
+
return false unless @chars.include? char
|
68
|
+
end
|
69
|
+
true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/smartdict/translator.rb
CHANGED
@@ -1,38 +1,33 @@
|
|
1
1
|
module Smartdict
|
2
2
|
class Translator
|
3
|
+
extend ActiveSupport::Autoload
|
3
4
|
|
4
|
-
|
5
|
-
|
5
|
+
autoload :Base
|
6
|
+
autoload :DriverConfiguration
|
7
|
+
autoload :LanguageDetector
|
6
8
|
|
7
|
-
|
8
|
-
self.from_lang_code = :en
|
9
|
+
attr_reader :default_opts
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
self.log_queries = true
|
15
|
-
|
16
|
-
|
17
|
-
def self.translate(word)
|
18
|
-
unless translation_model = Models::Translation.find(word, from_lang_code, to_lang_code, driver_name)
|
19
|
-
translation = driver.translate(word, from_lang_code, to_lang_code)
|
20
|
-
translation_model = Models::Translation.create_from_struct(translation)
|
21
|
-
end
|
22
|
-
log_query(translation_model) if log_queries
|
23
|
-
translation_model.to_struct
|
11
|
+
def initialize(default_opts = {})
|
12
|
+
@default_opts = default_opts
|
13
|
+
@middleware_classes = [Base, DriverConfiguration, LanguageDetector]
|
14
|
+
@middleware = build_middleware
|
24
15
|
end
|
25
16
|
|
26
|
-
def
|
27
|
-
|
17
|
+
def translate(word, opts = {})
|
18
|
+
opts.reverse_merge!(default_opts)
|
19
|
+
@middleware.last.call(word, opts)
|
28
20
|
end
|
29
21
|
|
30
22
|
|
31
23
|
private
|
32
24
|
|
33
|
-
def
|
34
|
-
|
25
|
+
def build_middleware
|
26
|
+
hooks = []
|
27
|
+
@middleware_classes.each do |klass|
|
28
|
+
hooks << klass.new(hooks.last)
|
29
|
+
end
|
30
|
+
hooks
|
35
31
|
end
|
36
|
-
|
37
32
|
end
|
38
33
|
end
|
data/lib/smartdict.rb
CHANGED
@@ -33,7 +33,8 @@ module Smartdict
|
|
33
33
|
autoload :Translator
|
34
34
|
autoload :Formats
|
35
35
|
autoload :Translation
|
36
|
-
autoload :
|
36
|
+
autoload :Log
|
37
|
+
autoload :Info
|
37
38
|
|
38
39
|
include Smartdict::Core
|
39
40
|
|
@@ -94,4 +95,8 @@ module Smartdict
|
|
94
95
|
def plugins_dir
|
95
96
|
ENV['SMARTDICT_PLUGINS_DIR'] or File.join(root_dir, 'plugins')
|
96
97
|
end
|
98
|
+
|
99
|
+
def info
|
100
|
+
Info.instance
|
101
|
+
end
|
97
102
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smartdict-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dm-core
|
@@ -139,6 +139,22 @@ dependencies:
|
|
139
139
|
- - ! '>='
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: nokogiri
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :runtime
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
142
158
|
- !ruby/object:Gem::Dependency
|
143
159
|
name: guard
|
144
160
|
requirement: !ruby/object:Gem::Requirement
|
@@ -388,7 +404,6 @@ extensions: []
|
|
388
404
|
extra_rdoc_files:
|
389
405
|
- README.markdown
|
390
406
|
files:
|
391
|
-
- ./bin/smartdict
|
392
407
|
- ./lib/smartdict-core.rb
|
393
408
|
- ./lib/smartdict.rb
|
394
409
|
- ./lib/smartdict/commands.rb
|
@@ -397,6 +412,7 @@ files:
|
|
397
412
|
- ./lib/smartdict/commands/help_command.rb
|
398
413
|
- ./lib/smartdict/commands/list_command.rb
|
399
414
|
- ./lib/smartdict/commands/translate_command.rb
|
415
|
+
- ./lib/smartdict/commands/version_command.rb
|
400
416
|
- ./lib/smartdict/core.rb
|
401
417
|
- ./lib/smartdict/core/command_manager.rb
|
402
418
|
- ./lib/smartdict/core/driver_manager.rb
|
@@ -408,13 +424,15 @@ files:
|
|
408
424
|
- ./lib/smartdict/drivers.rb
|
409
425
|
- ./lib/smartdict/drivers/abstract_driver.rb
|
410
426
|
- ./lib/smartdict/drivers/google_translate_driver.rb
|
427
|
+
- ./lib/smartdict/drivers/lingvo_yandex_driver.rb
|
411
428
|
- ./lib/smartdict/errors.rb
|
412
429
|
- ./lib/smartdict/formats.rb
|
413
430
|
- ./lib/smartdict/formats/abstract_format.rb
|
414
431
|
- ./lib/smartdict/formats/fb2_format.rb
|
415
432
|
- ./lib/smartdict/formats/text_color_format.rb
|
416
433
|
- ./lib/smartdict/formats/text_format.rb
|
417
|
-
- ./lib/smartdict/
|
434
|
+
- ./lib/smartdict/info.rb
|
435
|
+
- ./lib/smartdict/log.rb
|
418
436
|
- ./lib/smartdict/models.rb
|
419
437
|
- ./lib/smartdict/models/driver.rb
|
420
438
|
- ./lib/smartdict/models/language.rb
|
@@ -425,7 +443,6 @@ files:
|
|
425
443
|
- ./lib/smartdict/models/word_class.rb
|
426
444
|
- ./lib/smartdict/plugin.rb
|
427
445
|
- ./lib/smartdict/plugin/initializer_context.rb
|
428
|
-
- ./lib/smartdict/runner.rb
|
429
446
|
- ./lib/smartdict/storage.rb
|
430
447
|
- ./lib/smartdict/storage/seeder.rb
|
431
448
|
- ./lib/smartdict/storage/seeds/drivers.csv
|
@@ -433,13 +450,15 @@ files:
|
|
433
450
|
- ./lib/smartdict/storage/seeds/word_classes.csv
|
434
451
|
- ./lib/smartdict/translation.rb
|
435
452
|
- ./lib/smartdict/translator.rb
|
453
|
+
- ./lib/smartdict/translator/base.rb
|
454
|
+
- ./lib/smartdict/translator/driver_configuration.rb
|
455
|
+
- ./lib/smartdict/translator/language_detector.rb
|
436
456
|
- ./lib/smartdict/version.rb
|
437
457
|
- GPL-LICENSE.txt
|
438
458
|
- VERSION
|
459
|
+
- bin/smartdict
|
439
460
|
- config/default_config.yml
|
440
461
|
- README.markdown
|
441
|
-
- !binary |-
|
442
|
-
YmluL3NtYXJ0ZGljdA==
|
443
462
|
- !binary |-
|
444
463
|
YmluL3NtYXJ0ZGljdC1wb3B1bGF0b3I=
|
445
464
|
homepage: http://github.com/smartdict/smartdict-core
|
@@ -463,7 +482,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
463
482
|
version: '0'
|
464
483
|
requirements: []
|
465
484
|
rubyforge_project:
|
466
|
-
rubygems_version: 1.8.
|
485
|
+
rubygems_version: 1.8.24
|
467
486
|
signing_key:
|
468
487
|
specification_version: 3
|
469
488
|
summary: CLI dictionary
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# TODO: tests
|
2
|
-
|
3
|
-
class Smartdict::ListBuilder
|
4
|
-
include Smartdict::Models
|
5
|
-
|
6
|
-
# Options:
|
7
|
-
# * +:since+
|
8
|
-
# * +:till+
|
9
|
-
# * +:from_lang+
|
10
|
-
# * +:to_lang+
|
11
|
-
# * +:driver+
|
12
|
-
#
|
13
|
-
# @return [Array] array of {Smartdict::Translation}.
|
14
|
-
def self.build(options = {})
|
15
|
-
new(options).send(:build)
|
16
|
-
end
|
17
|
-
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def initialize(options)
|
22
|
-
@options = options
|
23
|
-
@options[:till] ||= Time.now
|
24
|
-
end
|
25
|
-
|
26
|
-
def build
|
27
|
-
fetch_translations.map(&:to_struct)
|
28
|
-
end
|
29
|
-
|
30
|
-
def fetch_translations
|
31
|
-
query = Translation.all(Translation.translation_queries.created_at.lte => @options[:till])
|
32
|
-
query = query.all(Translation.translation_queries.created_at.gte => @options[:since]) if @options[:since]
|
33
|
-
query = query.all(Translation.to_lang_id => to_lang.id) if to_lang
|
34
|
-
query = query.all(Translation.from_lang_id => from_lang.id) if from_lang
|
35
|
-
query = query.all(Translation.driver_id => driver.id) if driver
|
36
|
-
query
|
37
|
-
end
|
38
|
-
|
39
|
-
def from_lang
|
40
|
-
Language[@options[:from_lang]]
|
41
|
-
end
|
42
|
-
|
43
|
-
def to_lang
|
44
|
-
Language[@options[:to_lang]]
|
45
|
-
end
|
46
|
-
|
47
|
-
def driver
|
48
|
-
Driver[@options[:driver]]
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|