smartdict-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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