smartdict-core 0.0.1

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.
Files changed (50) hide show
  1. data/README.markdown +77 -0
  2. data/bin/smartdict +20 -0
  3. data/bin/smartdict-populator +50 -0
  4. data/config/default_config.yml +15 -0
  5. data/lib/smartdict/commands/abstract_command.rb +225 -0
  6. data/lib/smartdict/commands/has_format_list.rb +28 -0
  7. data/lib/smartdict/commands/help_command.rb +53 -0
  8. data/lib/smartdict/commands/list_command.rb +45 -0
  9. data/lib/smartdict/commands/translate_command.rb +35 -0
  10. data/lib/smartdict/commands.rb +10 -0
  11. data/lib/smartdict/core/command_manager.rb +27 -0
  12. data/lib/smartdict/core/driver_manager.rb +9 -0
  13. data/lib/smartdict/core/format_manager.rb +8 -0
  14. data/lib/smartdict/core/has_logger.rb +12 -0
  15. data/lib/smartdict/core/is_manager.rb +21 -0
  16. data/lib/smartdict/core/logger.rb +5 -0
  17. data/lib/smartdict/core/plugin_manager.rb +32 -0
  18. data/lib/smartdict/core.rb +12 -0
  19. data/lib/smartdict/drivers/abstract_driver.rb +61 -0
  20. data/lib/smartdict/drivers/google_translate_driver.rb +56 -0
  21. data/lib/smartdict/drivers.rb +6 -0
  22. data/lib/smartdict/errors.rb +11 -0
  23. data/lib/smartdict/formats/abstract_format.rb +21 -0
  24. data/lib/smartdict/formats/fb2_format.rb +40 -0
  25. data/lib/smartdict/formats/text_color_format.rb +52 -0
  26. data/lib/smartdict/formats/text_format.rb +9 -0
  27. data/lib/smartdict/formats.rb +8 -0
  28. data/lib/smartdict/list_builder.rb +51 -0
  29. data/lib/smartdict/models/driver.rb +14 -0
  30. data/lib/smartdict/models/language.rb +16 -0
  31. data/lib/smartdict/models/translated_word.rb +14 -0
  32. data/lib/smartdict/models/translation.rb +83 -0
  33. data/lib/smartdict/models/translation_query.rb +13 -0
  34. data/lib/smartdict/models/word.rb +13 -0
  35. data/lib/smartdict/models/word_class.rb +14 -0
  36. data/lib/smartdict/models.rb +11 -0
  37. data/lib/smartdict/plugin/initializer_context.rb +7 -0
  38. data/lib/smartdict/plugin.rb +9 -0
  39. data/lib/smartdict/runner.rb +5 -0
  40. data/lib/smartdict/storage/seeder.rb +32 -0
  41. data/lib/smartdict/storage/seeds/drivers.csv +2 -0
  42. data/lib/smartdict/storage/seeds/languages.csv +188 -0
  43. data/lib/smartdict/storage/seeds/word_classes.csv +10 -0
  44. data/lib/smartdict/storage.rb +44 -0
  45. data/lib/smartdict/translation.rb +19 -0
  46. data/lib/smartdict/translator.rb +38 -0
  47. data/lib/smartdict/version.rb +3 -0
  48. data/lib/smartdict-core.rb +1 -0
  49. data/lib/smartdict.rb +97 -0
  50. metadata +468 -0
@@ -0,0 +1,12 @@
1
+ module Smartdict::Core
2
+ extend ActiveSupport::Autoload
3
+
4
+ autoload :HasLogger
5
+ autoload :Logger
6
+ autoload :IsManager
7
+
8
+ autoload :PluginManager
9
+ autoload :CommandManager
10
+ autoload :DriverManager
11
+ autoload :FormatManager
12
+ end
@@ -0,0 +1,61 @@
1
+ # Translation driver provides an ability to translate words using external
2
+ # data sources. It can be data from local disk, database or remote services
3
+ # like Google Translate.
4
+ # Every driver must inherit {Smartdict::Driver} and have implementation of
5
+ # {#translate} method. This method should sets translated and transcription
6
+ # properties. For examples you can see #{Smartdict::Driver::GoogleTranslateDriver}.
7
+ #
8
+ # In your implementation of {#translate} you need to use methods:
9
+ # * word - returns a word what needs to be translated.
10
+ # * from_lang - code of source language("en", "ru", etc)
11
+ # * to_lang - code of target language("en, "ru, etc)
12
+ #
13
+ # Usage:
14
+ # class HelloWorldDriver < Smartdict::Driver
15
+ # # Set name of driver, so DriverManager can identify it.
16
+ # name "hello_world"
17
+ #
18
+ # # This trivial example will always return the same translation.
19
+ # def translate
20
+ # self.translated = {"noun" => ["hello", "hi"], "verb" => ["greet"]}
21
+ # self.transcription = "he'leu"
22
+ # end
23
+ # end
24
+ class Smartdict::Drivers::AbstractDriver
25
+
26
+ attr_reader :word, :from_lang, :to_lang
27
+ attr_accessor :translated, :transcription
28
+
29
+ # Is used to identify a driver
30
+ cattr_accessor :name
31
+
32
+ def self.translate(*args)
33
+ self.new(*args).build_translation
34
+ end
35
+
36
+ # Sets driver name
37
+ def self.set_name(name)
38
+ self.name = name.to_s
39
+ end
40
+
41
+ def initialize(word, from_lang, to_lang)
42
+ @word = word
43
+ @from_lang = from_lang
44
+ @to_lang = to_lang
45
+ translate
46
+ end
47
+
48
+ def build_translation
49
+ translation = Smartdict::Translation.new(
50
+ :word => word,
51
+ :from_lang => from_lang.to_s,
52
+ :to_lang => to_lang.to_s,
53
+ :transcription => transcription,
54
+ :driver => self.name
55
+ )
56
+ translated.each do |word_class, words|
57
+ translation.translated[word_class] = words
58
+ end
59
+ translation
60
+ end
61
+ end
@@ -0,0 +1,56 @@
1
+ module Smartdict::Drivers
2
+ # The translation driver for Google Translate service.
3
+ class GoogleTranslateDriver < AbstractDriver
4
+
5
+ # Pretend being Firefox :)
6
+ 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)"
7
+
8
+ # Host of Google Translate service.
9
+ HOST = "translate.google.com"
10
+
11
+ set_name "google_translate"
12
+
13
+ # Sets translation and transcription. GoogleTranslate doesn't provide
14
+ # transcription, so it's nil.
15
+ def translate
16
+ self.translated = response_to_hash(get_response)
17
+ self.transcription = nil
18
+ end
19
+
20
+
21
+ private
22
+
23
+ # Parses string return by GoogleTranslate
24
+ # Returns hash similar to this:
25
+ # { "noun" => ["try", "attempt"],
26
+ # "verb" => ["try", "offer"] }
27
+ # If no translation was found than returns nil.
28
+ # @return [Hash] every key is word class and every value is array of translated words.
29
+ def response_to_hash(response)
30
+ result = {}
31
+ array = YAML.load(response.gsub(/,+/, ', '))
32
+ array[1].each do |trans|
33
+ word_class = trans[0].empty? ? 'undefined' : trans[0]
34
+ result[word_class] = trans[1]
35
+ end
36
+ result
37
+ rescue NoMethodError
38
+ raise Smartdict::TranslationNotFound
39
+ end
40
+
41
+ # Sends remote request to GooogleTranslate service.
42
+ # @return [String] body of response.
43
+ def get_response
44
+ http = Net::HTTP.new(HOST, 80)
45
+ request = Net::HTTP::Get.new(http_path, {"User-Agent" => USER_AGENT})
46
+ http.request(request).read_body
47
+ end
48
+
49
+ # @return [String] http path for request to translate word.
50
+ def http_path
51
+ w = word.gsub(' ', '+')
52
+ "/translate_a/t?client=t&text=#{w}&hl=en&sl=#{from_lang}&tl=#{to_lang}&multires=1&otf=1&rom=1&ssel=0&tsel=0&sc=1"
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,6 @@
1
+ module Smartdict::Drivers
2
+ extend ActiveSupport::Autoload
3
+
4
+ autoload :AbstractDriver
5
+ autoload :GoogleTranslateDriver
6
+ end
@@ -0,0 +1,11 @@
1
+ module Smartdict
2
+
3
+ class Error < ::Exception; end
4
+
5
+ class TranslationNotFound < Error
6
+ def initialize(msg = "Translation is not found")
7
+ super(msg)
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,21 @@
1
+ class Smartdict::Formats::AbstractFormat
2
+ include Singleton
3
+
4
+ class_attribute :description
5
+
6
+ def self.set_description(description)
7
+ self.description = description
8
+ end
9
+
10
+ # @param [Models::Translation] translation
11
+ # @return [String]
12
+ def self.format_translation(translation)
13
+ instance.format_translation(translation)
14
+ end
15
+
16
+ # @param [Array] array of {Models::Translation}.
17
+ # @return [String]
18
+ def self.format_list(translations)
19
+ instance.format_list(translations)
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ require 'builder'
2
+
3
+ class Smartdict::Formats::Fb2Format < Smartdict::Formats::AbstractFormat
4
+ set_description "Displays translation in fiction book format."
5
+
6
+
7
+ def format_list(translations)
8
+ xml = Builder::XmlMarkup.new(:indent => 2)
9
+ xml.instruct!
10
+ xml.FictionBook(:xmlns => "http://www.gribuser.ru/xml/fictionbook/2.0") do |book|
11
+ book.description do |desc|
12
+ desc.tag!("document-info") do |doc_info|
13
+ doc_info.tag!("program-used", "Smartdict version #{Smartdict::VERSION}")
14
+ end
15
+ desc.tag!('title-info') do |title_info|
16
+ title_info.tag!('book-title', "#{Time.now.strftime('%F')} - English words")
17
+ title_info.genre 'sci_linguistic'
18
+ title_info.annotation do |annotation|
19
+ annotation.p "English words to learn"
20
+ annotation.p "The content generate by program Smardict v#{Smartdict::VERSION}"
21
+ end
22
+ end
23
+ end
24
+ book.body do |body|
25
+ body.title "TITLE"
26
+ translations.each do |translation|
27
+ body.section do |word_section|
28
+ word_section.title {|title| title.p "#{translation.word} [#{translation.transcription}]"}
29
+ translation.translated.each do |word_class, translations|
30
+ word_section.subtitle word_class
31
+ word_section.p translations.join("; ")
32
+ word_section.tag!("empty-line")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,52 @@
1
+ class Smartdict::Formats::TextColorFormat < Smartdict::Formats::AbstractFormat
2
+ set_description "Displays translation with ASCII highlight"
3
+
4
+ # :nodoc:
5
+ def format_translation(translation)
6
+ result = "#{bold_green(translation.word)}"
7
+ result << green(" [#{translation.transcription}]") if translation.transcription
8
+ result << "\n"
9
+
10
+ translation.translated.each do |word_class, words|
11
+ result << " #{bold(word_class)}\n"
12
+ words.each do |word|
13
+ result << " #{word}\n"
14
+ end
15
+ end
16
+
17
+ result
18
+ end
19
+
20
+ # :nodoc:
21
+ def format_list(translations)
22
+ result = ""
23
+ translations.each do |translation|
24
+ translated_words = translation.translated.values.map{|words| words.first(3)}.flatten
25
+
26
+ result << green(translation.word)
27
+ result << " - "
28
+ result << translated_words.join(", ")
29
+ result << "\n"
30
+ end
31
+ result
32
+ end
33
+
34
+
35
+ private
36
+
37
+ def bold_green(text)
38
+ colorize(text, "1;32")
39
+ end
40
+
41
+ def bold(text)
42
+ colorize(text, 1)
43
+ end
44
+
45
+ def green(text)
46
+ colorize(text, 32)
47
+ end
48
+
49
+ def colorize(text, color_code)
50
+ "\e[#{color_code}m#{text}\e[0m"
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ class Smartdict::Formats::TextFormat < Smartdict::Formats::TextColorFormat
2
+ set_description "Displays translation in pure text"
3
+
4
+ private
5
+
6
+ def colorize(text, color_code)
7
+ text
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Smartdict::Formats
2
+ extend ActiveSupport::Autoload
3
+
4
+ autoload :AbstractFormat
5
+ autoload :TextFormat
6
+ autoload :TextColorFormat
7
+ autoload :Fb2Format
8
+ end
@@ -0,0 +1,51 @@
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
@@ -0,0 +1,14 @@
1
+ class Smartdict::Models::Driver
2
+ include DataMapper::Resource
3
+ include DataMapper::Validations
4
+ include DataMapper::Enum
5
+
6
+ acts_as_enumerated
7
+
8
+ property :id , Serial
9
+ property :name, String, :unique_index => true
10
+
11
+ has n, :translations
12
+
13
+ validates_presence_of :name
14
+ end
@@ -0,0 +1,16 @@
1
+ class Smartdict::Models::Language
2
+ include DataMapper::Resource
3
+ include DataMapper::Validations
4
+ include DataMapper::Enum
5
+
6
+ acts_as_enumerated :name_property => :code
7
+
8
+ property :id , Serial
9
+ property :name, String
10
+ property :code, String, :unique_index => true, :length => 2
11
+
12
+ has n, :words
13
+
14
+ validates_presence_of :name, :code
15
+ validates_uniqueness_of :name, :code
16
+ end
@@ -0,0 +1,14 @@
1
+ class Smartdict::Models::TranslatedWord
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+ property :translation_id, Integer, :unique_index => :index_translated_word
6
+ property :word_class_id , Integer, :unique_index => :index_translated_word
7
+ property :word_id , Integer, :unique_index => :index_translated_word
8
+
9
+ belongs_to :translation
10
+ belongs_to :word_class
11
+ belongs_to :word
12
+
13
+ validates_presence_of :translation_id, :word_class_id, :word_id
14
+ end
@@ -0,0 +1,83 @@
1
+ class Smartdict::Models::Translation
2
+ include DataMapper::Resource
3
+ include Smartdict::Models
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
10
+
11
+ belongs_to :word
12
+ belongs_to :driver
13
+ belongs_to :from_lang, 'Language'
14
+ belongs_to :to_lang , 'Language'
15
+ has n, :translated_words
16
+ has n, :translation_queries
17
+
18
+ validates_presence_of :word
19
+ validates_presence_of :driver
20
+ validates_presence_of :from_lang_id
21
+ validates_presence_of :to_lang_id
22
+
23
+
24
+ # Create {Smartdict::Models::Translation} from {Smartdict::Translation}.
25
+ def self.create_from_struct(struct)
26
+ from_lang = Language[struct.from_lang]
27
+ to_lang = Language[struct.to_lang]
28
+ driver = Driver[struct.driver]
29
+
30
+ word = Word.first_or_create(:name => struct.word, :language_id => from_lang.id)
31
+ word.transcription = struct.transcription if word.transcription.blank?
32
+ word.save!
33
+
34
+ translation = self.create(
35
+ :word => word,
36
+ :driver => driver,
37
+ :from_lang_id => from_lang.id,
38
+ :to_lang_id => to_lang.id
39
+ )
40
+
41
+ struct.translated.each do |word_class_name, meanings|
42
+ meanings.each do |meaning|
43
+ w = Word.first_or_create(:name => meaning, :language_id => to_lang.id)
44
+ word_class = WordClass[word_class_name]
45
+ TranslatedWord.create(:word => w, :word_class => word_class, :translation => translation)
46
+ end
47
+ end
48
+
49
+ translation
50
+ end
51
+
52
+ def self.find(word, from_lang_code, to_lang_code, driver_name)
53
+ from_lang = Language[from_lang_code]
54
+ to_lang = Language[to_lang_code]
55
+ driver = Driver[driver_name]
56
+
57
+ word = Word.first(:name => word, :language_id => from_lang.id)
58
+ self.first(:from_lang => from_lang, :to_lang => to_lang, :word => word, :driver => driver)
59
+ end
60
+
61
+
62
+ # TODO: it's a hack. Remove.
63
+ def initialize(*args)
64
+ self.class.finalize
65
+ self.word_class_id = 1
66
+ super(*args)
67
+ end
68
+
69
+ def to_struct
70
+ struct = Smartdict::Translation.new(
71
+ :word => word.name,
72
+ :transcription => word.transcription,
73
+ :from_lang => from_lang.code,
74
+ :to_lang => to_lang.code,
75
+ :driver => driver.name
76
+ )
77
+ translated_words.each do |tw|
78
+ struct[tw.word_class.name] << tw.word.name
79
+ end
80
+ struct
81
+ end
82
+
83
+ end
@@ -0,0 +1,13 @@
1
+ class Smartdict::Models::TranslationQuery
2
+ include DataMapper::Resource
3
+
4
+ property :id , Serial
5
+ property :created_at , DateTime
6
+ property :translation_id, Integer
7
+
8
+ belongs_to :translation
9
+
10
+ before :save do
11
+ self.created_at = Time.now
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class Smartdict::Models::Word
2
+ include DataMapper::Resource
3
+
4
+ property :id , Serial
5
+ property :name , String, :unique_index => :index_word
6
+ property :language_id , Integer, :unique_index => :index_word
7
+ property :transcription, String
8
+
9
+ belongs_to :language
10
+ has n, :translations
11
+
12
+ validates_presence_of :language_id, :name
13
+ end
@@ -0,0 +1,14 @@
1
+ # Word class is the same what type of speech is (noun, verb, etc.)
2
+ class Smartdict::Models::WordClass
3
+ include DataMapper::Resource
4
+ include DataMapper::Enum
5
+
6
+ acts_as_enumerated
7
+
8
+ property :id, Serial
9
+ property :name, String
10
+
11
+ has n, :translations
12
+
13
+ validates_presence_of :name
14
+ end
@@ -0,0 +1,11 @@
1
+ module Smartdict
2
+ module Models; end
3
+ end
4
+
5
+ require 'smartdict/models/language'
6
+ require 'smartdict/models/translation'
7
+ require 'smartdict/models/translation_query'
8
+ require 'smartdict/models/word'
9
+ require 'smartdict/models/word_class'
10
+ require 'smartdict/models/translated_word'
11
+ require 'smartdict/models/driver'
@@ -0,0 +1,7 @@
1
+ class Smartdict::Plugin::InitializerContext
2
+ include Smartdict::Core
3
+
4
+ def register_command(name, command_class)
5
+ CommandManager.register(name, command_class)
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ class Smartdict::Plugin
2
+ extend ActiveSupport::Autoload
3
+
4
+ autoload :InitializerContext
5
+
6
+ def self.initializer(name, options = {}, &block)
7
+ Smartdict::Core::PluginManager.register(name, :options => options, :block => block)
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Smartdict::Runner
2
+ def self.run(args)
3
+ Smartdict::Core::CommandManager.run(args)
4
+ end
5
+ end
@@ -0,0 +1,32 @@
1
+ require 'csv'
2
+
3
+ module Smartdict::Storage::Seeder
4
+ extend self
5
+
6
+ def seed!
7
+ seed_files.each do |csv_file|
8
+ seed_from(csv_file)
9
+ end
10
+ end
11
+
12
+
13
+ private
14
+
15
+ def seed_files
16
+ seeds_dir = File.expand_path('../seeds', __FILE__)
17
+ Dir["#{seeds_dir}/*.csv"]
18
+ end
19
+
20
+ def seed_from(csv_file)
21
+ model = csv_file_to_model(csv_file)
22
+ CSV.read(csv_file, :headers => true).each do |row|
23
+ model.create(row.to_hash)
24
+ end
25
+ end
26
+
27
+ def csv_file_to_model(csv_file)
28
+ plural_name = File.basename(csv_file).gsub(/\.csv$/, "")
29
+ model_name = plural_name.classify
30
+ Smartdict::Models.const_get(model_name)
31
+ end
32
+ end
@@ -0,0 +1,2 @@
1
+ id,name
2
+ 1,google_translate