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 CHANGED
@@ -1,6 +1,9 @@
1
- # Smartdict
1
+ # smartdict-core [![Build Status](https://secure.travis-ci.org/smartdict/smartdict-core.png)](http://travis-ci.org/smartdict/smartdict-core)
2
2
 
3
- Simple dictionary.
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.2
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
- # TODO: remove before release
10
- # require 'pry'
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::Runner.run(args)
16
+ Smartdict::Core::CommandManager.run(args)
@@ -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
- Smartdict::Translator.translate(word)
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
- Smartdict::Translator.from_lang_code = from_lang.code
41
- Smartdict::Translator.to_lang_code = to_lang.code
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)
@@ -13,3 +13,7 @@ default:
13
13
  - de
14
14
  - es
15
15
  - fr
16
+
17
+ drivers:
18
+ default: google_translate
19
+ en-ru: lingvo_yandex
@@ -24,14 +24,14 @@ module Smartdict::Commands
24
24
  :driver => nil
25
25
 
26
26
  def execute
27
- list_opts = {
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::ListBuilder.build(list_opts)
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
- Smartdict::Translator.from_lang_code = @options[:from]
23
- Smartdict::Translator.to_lang_code = @options[:to]
24
- translation = Smartdict::Translator.translate(@arguments[:word])
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
@@ -3,6 +3,7 @@ module Smartdict::Commands
3
3
 
4
4
  autoload :AbstractCommand
5
5
  autoload :HelpCommand
6
+ autoload :VersionCommand
6
7
  autoload :TranslateCommand
7
8
  autoload :ListCommand
8
9
 
@@ -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
@@ -6,4 +6,5 @@ class Smartdict::Core::DriverManager
6
6
  include Smartdict::Drivers
7
7
 
8
8
  register 'google_translate', GoogleTranslateDriver
9
+ register 'lingvo_yandex' , LingvoYandexDriver
9
10
  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
- cattr_accessor :name
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
@@ -3,4 +3,5 @@ module Smartdict::Drivers
3
3
 
4
4
  autoload :AbstractDriver
5
5
  autoload :GoogleTranslateDriver
6
+ autoload :LingvoYandexDriver
6
7
  end
@@ -1,6 +1,7 @@
1
1
  module Smartdict
2
2
 
3
- class Error < ::Exception; end
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 , 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
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 => word,
36
- :driver => driver,
37
- :from_lang_id => from_lang.id,
38
- :to_lang_id => 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. Remove.
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 => word.transcription,
73
+ :transcription => self.transcription,
73
74
  :from_lang => from_lang.code,
74
75
  :to_lang => to_lang.code,
75
76
  :driver => driver.name
@@ -4,7 +4,6 @@ class Smartdict::Models::Word
4
4
  property :id , Serial
5
5
  property :name , String, :unique_index => :index_word
6
6
  property :language_id , Integer, :unique_index => :index_word
7
- property :transcription, String
8
7
 
9
8
  belongs_to :language
10
9
  has n, :translations
@@ -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.read(csv_file, :headers => true).each do |row|
23
- model.create(row.to_hash)
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
@@ -1,2 +1,3 @@
1
1
  id,name
2
2
  1,google_translate
3
+ 2,lingvo_yandex
@@ -7,4 +7,6 @@ id,name
7
7
  6,numeral
8
8
  7,interjection
9
9
  8,abbreviation
10
- 9,other
10
+ 9,pronoun
11
+ 10,conjunction
12
+ 11,other
@@ -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
@@ -1,38 +1,33 @@
1
1
  module Smartdict
2
2
  class Translator
3
+ extend ActiveSupport::Autoload
3
4
 
4
- class_attribute :driver_name
5
- self.driver_name = :google_translate
5
+ autoload :Base
6
+ autoload :DriverConfiguration
7
+ autoload :LanguageDetector
6
8
 
7
- class_attribute :from_lang_code
8
- self.from_lang_code = :en
9
+ attr_reader :default_opts
9
10
 
10
- class_attribute :to_lang_code
11
- self.to_lang_code = :ru
12
-
13
- class_attribute :log_queries
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 self.log_query(translation)
27
- Models::TranslationQuery.create(:translation => translation)
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 self.driver
34
- Smartdict::Core::DriverManager.find(driver_name)
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 :ListBuilder
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.2
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-04-09 00:00:00.000000000 Z
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/list_builder.rb
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.21
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
@@ -1,5 +0,0 @@
1
- class Smartdict::Runner
2
- def self.run(args)
3
- Smartdict::Core::CommandManager.run(args)
4
- end
5
- end