smartdict-core 0.0.2 → 0.1.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.
- 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 [](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
|