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 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