abbyy-cloud 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +24 -0
  4. data/README.md +60 -33
  5. data/abbyy-cloud.gemspec +1 -1
  6. data/lib/abbyy/cloud.rb +10 -16
  7. data/lib/abbyy/cloud/connection.rb +31 -17
  8. data/lib/abbyy/cloud/models/direction.rb +4 -2
  9. data/lib/abbyy/cloud/models/discount.rb +12 -0
  10. data/lib/abbyy/cloud/models/engine.rb +4 -1
  11. data/lib/abbyy/cloud/models/locale.rb +23 -0
  12. data/lib/abbyy/cloud/models/price.rb +22 -0
  13. data/lib/abbyy/cloud/models/source_segment.rb +14 -0
  14. data/lib/abbyy/cloud/models/source_tag.rb +15 -0
  15. data/lib/abbyy/cloud/models/transfer_data.rb +14 -0
  16. data/lib/abbyy/cloud/models/translation.rb +1 -1
  17. data/lib/abbyy/cloud/models/translation_segment.rb +16 -0
  18. data/lib/abbyy/cloud/models/unit_price.rb +13 -0
  19. data/lib/abbyy/cloud/namespaces/machine_translations.rb +47 -1
  20. data/lib/abbyy/cloud/namespaces/prices.rb +29 -0
  21. data/lib/abbyy/cloud/operations/base.rb +20 -6
  22. data/lib/abbyy/cloud/operations/engines.rb +3 -1
  23. data/lib/abbyy/cloud/operations/prices.rb +23 -0
  24. data/lib/abbyy/cloud/operations/translate.rb +5 -3
  25. data/lib/abbyy/cloud/operations/translate_segments.rb +22 -0
  26. data/lib/abbyy/cloud/settings.rb +4 -5
  27. data/lib/abbyy/cloud/types.rb +14 -10
  28. data/spec/abbyy/cloud/connection_spec.rb +1 -1
  29. data/spec/abbyy/cloud/models/discount_spec.rb +32 -0
  30. data/spec/abbyy/cloud/models/locale_spec.rb +55 -0
  31. data/spec/abbyy/cloud/models/price_spec.rb +107 -0
  32. data/spec/abbyy/cloud/models/source_segment_spec.rb +37 -0
  33. data/spec/abbyy/cloud/models/source_tag_spec.rb +56 -0
  34. data/spec/abbyy/cloud/models/transfer_data_spec.rb +40 -0
  35. data/spec/abbyy/cloud/models/translation_segment_spec.rb +38 -0
  36. data/spec/abbyy/cloud/models/unit_price_spec.rb +48 -0
  37. data/spec/abbyy/cloud/settings_spec.rb +2 -24
  38. data/spec/abbyy/cloud_spec.rb +3 -4
  39. data/spec/feature/abbyy/mt_default_engine_spec.rb +12 -0
  40. data/spec/feature/abbyy/mt_engine_spec.rb +15 -0
  41. data/spec/feature/abbyy/mt_translate_segments_spec.rb +136 -0
  42. data/spec/feature/abbyy/{order_translate_spec.rb → mt_translate_spec.rb} +2 -2
  43. data/spec/feature/abbyy/prices_details_spec.rb +70 -0
  44. metadata +39 -5
  45. data/lib/abbyy/cloud/namespaces/orders.rb +0 -25
@@ -3,7 +3,7 @@ class ABBYY::Cloud
3
3
  module Models
4
4
  # Result of the order translation
5
5
  class Translation < Struct
6
- attribute :id, Types::OrderId
6
+ attribute :id, Types::Strict::String
7
7
  attribute :translation, Types::Strict::String
8
8
  end
9
9
  end
@@ -0,0 +1,16 @@
1
+ require_relative "transfer_data"
2
+
3
+ class ABBYY::Cloud
4
+ module Models
5
+ # A segment of the translation
6
+ class TranslationSegment < Struct
7
+ attribute :id, Types::Strict::String
8
+ attribute :text, Types::Strict::String
9
+ attribute :tags_transfer_data,
10
+ Types::Array.member(Types::TransferData).default([])
11
+ end
12
+
13
+ # Registers type Types::TranslationSegment
14
+ Types.register_type TranslationSegment
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ class ABBYY::Cloud
2
+ module Models
3
+ # Price details
4
+ class UnitPrice < Struct
5
+ attribute :unit_type, Types::UnitType
6
+ attribute :currency, Types::Currency
7
+ attribute :amount, Types::Coercible::Float
8
+ end
9
+
10
+ # Registers type Types::UnitPrice
11
+ Types.register_type UnitPrice
12
+ end
13
+ end
@@ -1,17 +1,63 @@
1
+ require_relative "base"
2
+
1
3
  class ABBYY::Cloud
2
4
  module Namespaces
3
5
  # Namespace for operations with machine translations
4
6
  # @see [https://api.abbyy.cloud/swagger/ui/index#!/MachineTranslation]
5
7
  class MachineTranslations < Base
6
8
  # Returns list of all available engines
9
+ # @return [Array<ABBYY::Cloud::Models::Engine>]
7
10
  def engines
8
11
  Operations::Engines.new(settings).call
9
12
  end
10
13
 
11
- # Returns settigns for the engine selected by its name
14
+ # Returns engine object selected by its name
15
+ # @return [ABBYY::Cloud::Models::Engine]
12
16
  def engine(name)
13
17
  engines.find { |engine| engine.name == name }
14
18
  end
19
+
20
+ # Returns engine object for the default engine
21
+ # @return [ABBYY::Cloud::Models::Engine]
22
+ def default_engine
23
+ engine(settings.engine)
24
+ end
25
+
26
+ # Instantly (synchronously) translates the text
27
+ #
28
+ # @example
29
+ # translate "Hello world!", from: "en", to: "fr_FR"
30
+ #
31
+ # @param [String] text
32
+ # @option [ABBYY::Cloud::Types::Locale] :from Source language
33
+ # @option [ABBYY::Cloud::Types::Locale] :to Target language
34
+ # @return [ABBYY::Cloud::Models::Translation]
35
+ #
36
+ def translate(text, from:, to:, **opts)
37
+ Operations::Translate.new(settings).call source_text: text,
38
+ source_language: from,
39
+ target_language: to,
40
+ engine: settings.engine,
41
+ **opts
42
+ end
43
+
44
+ # Instantly (synchronously) translates array of texts
45
+ #
46
+ # @example
47
+ # translate ["Hello", "world"], from: "en", to: "fr_FR"
48
+ #
49
+ # @param [Array<String>] texts
50
+ # @option [ABBYY::Cloud::Types::Locale] :from Source language
51
+ # @option [ABBYY::Cloud::Types::Locale] :to Target language
52
+ # @return [ABBYY::Cloud::Models::Translation]
53
+ #
54
+ def translate_segments(texts, from:, to:, **opts)
55
+ sources = texts.map { |text| { text: text } }
56
+ Operations::TranslateSegments
57
+ .new(settings)
58
+ .call sources: sources, from: from, to: to, engine: settings.engine,
59
+ **opts
60
+ end
15
61
  end
16
62
  end
17
63
  end
@@ -0,0 +1,29 @@
1
+ require_relative "base"
2
+
3
+ class ABBYY::Cloud
4
+ module Namespaces
5
+ # Namespace for operations with prices
6
+ # @see [https://api.abbyy.cloud/swagger/ui/index#!/Prices] ABBYY Cloud API
7
+ class Prices < Base
8
+ MAX_PER_REQUEST = 1_000
9
+
10
+ # Returns prices data filtered by type of translated object,
11
+ # source and target language.
12
+ #
13
+ # If take option is NOT set, the method makes as many requests
14
+ # as necessary to get all prices.
15
+ #
16
+ def details(skip: 0, take: nil, **opts)
17
+ take_now = (take && take <= MAX_PER_REQUEST) ? take : MAX_PER_REQUEST
18
+ take_later = take - take_now if take
19
+ skip_later = skip + take_now
20
+
21
+ items = Operations::Prices.new(settings)
22
+ .call(skip: skip, take: take_now, **opts)
23
+
24
+ return items if (items.count < take_now) || take_later.zero?
25
+ items + details(skip: skip_later, take: take_later, **opts)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -27,6 +27,10 @@ class ABBYY::Cloud
27
27
  provide_struct :@request_body, struct, &block
28
28
  end
29
29
 
30
+ def request_query(struct = nil, &block)
31
+ provide_struct :@request_query, struct, &block
32
+ end
33
+
30
34
  def response_body(struct = nil, &block)
31
35
  provide_struct :@response_body, struct, &block
32
36
  end
@@ -44,20 +48,24 @@ class ABBYY::Cloud
44
48
  end
45
49
  end
46
50
 
51
+ include Dry::Initializer.define -> do
52
+ param :settings
53
+ end
54
+
55
+ def_delegators :settings, :connection
47
56
  def_delegators :"self.class",
48
57
  :link,
49
58
  :http_method,
50
59
  :path,
51
60
  :request_body,
61
+ :request_query,
52
62
  :response_body
53
63
 
54
- include Dry::Initializer.define -> do
55
- param :settings
56
- end
57
-
58
64
  def call(**data)
59
- body = prepare_request_body(data)
60
- res = settings.connection.call(http_method, path, body: body)
65
+ body = prepare_request_body(data)
66
+ query = prepare_request_query(data)
67
+ res = connection.call(http_method, path, body: body, query: query)
68
+
61
69
  handle_response_body(res)
62
70
  end
63
71
 
@@ -69,6 +77,12 @@ class ABBYY::Cloud
69
77
  raise ArgumentError.new(link, data, error.message)
70
78
  end
71
79
 
80
+ def prepare_request_query(data)
81
+ request_query[data]
82
+ rescue => error
83
+ raise ArgumentError.new(link, data, error.message)
84
+ end
85
+
72
86
  def handle_response_body(data)
73
87
  response_body[data]
74
88
  rescue => error
@@ -1,10 +1,12 @@
1
+ require_relative "base"
2
+
1
3
  class ABBYY::Cloud
2
4
  module Operations
3
5
  class Engines < Base
4
6
  # rubocop: disable Metrics/LineLength
5
7
  link "https://api.abbyy.cloud/swagger/ui/index#!/MachineTranslation/MachineTranslation_Engines"
6
8
  # rubocop: enable Metrics/LineLength
7
- path "mt/engines"
9
+ path "v0/mt/engines"
8
10
  http_method "get"
9
11
 
10
12
  response_body Types::Array.member(Types::Engine)
@@ -0,0 +1,23 @@
1
+ require_relative "base"
2
+
3
+ class ABBYY::Cloud
4
+ module Operations
5
+ class Prices < Base
6
+ # rubocop: disable Metrics/LineLength
7
+ link "https://api.abbyy.cloud/swagger/ui/index#!/Prices/Prices_GetAccountPrices"
8
+ # rubocop: enable Metrics/LineLength
9
+ path "v0/prices/details"
10
+ http_method "get"
11
+
12
+ request_query do
13
+ attribute :skip, Types::Strict::Int
14
+ attribute :take, Types::Strict::Int
15
+ attribute :type, Types::Strict::String.optional
16
+ attribute :from, Types::Locale.optional
17
+ attribute :to, Types::Locale.optional
18
+ end
19
+
20
+ response_body Types::Array.member(Types::Price)
21
+ end
22
+ end
23
+ end
@@ -1,14 +1,16 @@
1
+ require_relative "base"
2
+
1
3
  class ABBYY::Cloud
2
4
  module Operations
3
5
  class Translate < Base
4
6
  link "https://api.abbyy.cloud/swagger/ui/index#!/Order/Order_Translate"
5
- path "order/mt/sync"
7
+ path "v0/order/mt/sync"
6
8
  http_method "post"
7
9
 
8
10
  request_body do
9
11
  attribute :engine, Types::Strict::String
10
- attribute :source_language, Types::Language
11
- attribute :target_language, Types::Language
12
+ attribute :source_language, Types::Locale
13
+ attribute :target_language, Types::Locale
12
14
  attribute :source_text, Types::Strict::String
13
15
  end
14
16
 
@@ -0,0 +1,22 @@
1
+ require_relative "base"
2
+
3
+ class ABBYY::Cloud
4
+ module Operations
5
+ class TranslateSegments < Base
6
+ # rubocop: disable Metrics/LineLength
7
+ link "https://api.abbyy.cloud/swagger/ui/index#!/Order/Order_TranslateSegments"
8
+ # rubocop: enable Metrics/LineLength
9
+ path "v1/order/mt/sync"
10
+ http_method "post"
11
+
12
+ request_body do
13
+ attribute :engine, Types::Strict::String
14
+ attribute :from, Types::Locale
15
+ attribute :to, Types::Locale
16
+ attribute :sources, Types::Array.member(Types::SourceSegment)
17
+ end
18
+
19
+ response_body Types::Array.member(Types::TranslationSegment)
20
+ end
21
+ end
22
+ end
@@ -3,14 +3,13 @@
3
3
  class ABBYY::Cloud
4
4
  class Settings
5
5
  include Dry::Initializer.define -> do
6
- option :id, type: Types::AuthId
7
- option :token, type: Types::AuthToken
8
- option :version, type: Types::Version, default: proc { 0 }
9
- option :engine, type: Types::Strict::String, default: proc { "Sandbox" }
6
+ option :id, type: Types::Strict::String
7
+ option :token, type: Types::Strict::String
8
+ option :engine, type: Types::Strict::String, default: proc { "Sandbox" }
10
9
  end
11
10
 
12
11
  def connection
13
- @connection ||= Connection.new(version: version, id: id, token: token)
12
+ @connection ||= Connection.new(id: id, token: token)
14
13
  end
15
14
  end
16
15
  end
@@ -2,27 +2,31 @@ module ABBYY::Cloud::Types
2
2
  include Dry::Types.module
3
3
 
4
4
  VERSIONS = [0].freeze
5
- LANGUAGE = /\A[A-Za-z]{2,}(-[A-Za-z]+)*\z/
5
+ UNIT_TYPES = %w(Chars Words Pages Documents).freeze
6
+ DISCOUNT_TYPES = \
7
+ %w(TMTextMatch TMTaggedTextMatch TMHalfContextMatch TMFullContextMatch)
8
+ .freeze
6
9
 
7
10
  # Gem-specific primitive types
8
- AuthId = Strict::String
9
- AuthToken = Strict::String
10
- Language = Strict::String.constrained(format: LANGUAGE)
11
- OrderId = Strict::String
12
- Version = Coercible::Int.constrained(included_in: VERSIONS)
11
+ Version = Coercible::Int.constrained(included_in: VERSIONS)
12
+ UnitType = Strict::String.constrained(included_in: UNIT_TYPES)
13
+ DiscountType = Strict::String.constrained(included_in: DISCOUNT_TYPES)
14
+ Currency = Strict::String.constrained(format: /\A[A-Z]{3}\z/)
13
15
 
14
16
  # Registers new coercible type from a struct class
15
- def self.register_type(struct, as: nil)
16
- type_name = as || struct.name.split("::").last.downcase
17
+ def self.register_type(struct, as: nil, constructor: :new)
18
+ type_name = Inflecto.underscore(as || struct.name.split("::").last)
17
19
  definition = Dry::Types::Definition.new(struct).constructor do |value|
18
20
  case value
19
- when nil then nil
21
+ when nil then raise
20
22
  when struct then value
21
- else struct[value]
23
+ else struct.send(constructor, value)
22
24
  end
23
25
  end
24
26
 
25
27
  Dry::Types.register type_name, definition
26
28
  Dry::Types.define_constants self, [type_name]
27
29
  end
30
+
31
+ register_type ::Time, as: "coercible.time", constructor: :parse
28
32
  end
@@ -1,5 +1,5 @@
1
1
  RSpec.describe ABBYY::Cloud::Connection do
2
- let(:connection) { described_class.new version: 0, id: "baz", token: "qux" }
2
+ let(:connection) { described_class.new id: "baz", token: "qux" }
3
3
 
4
4
  subject do
5
5
  connection.call :post,
@@ -0,0 +1,32 @@
1
+ RSpec.describe ABBYY::Cloud::Models::Discount do
2
+ let(:data) { { discount_type: "TMTextMatch", discount: 0.01 } }
3
+
4
+ subject { described_class.new(data) }
5
+
6
+ it { is_expected.to be_kind_of ABBYY::Cloud::Struct }
7
+ its(:to_h) { is_expected.to eq data }
8
+
9
+ context "with invalid discount_type:" do
10
+ before { data[:discount_type] = "Unknown" }
11
+
12
+ it "fails" do
13
+ expect { subject }.to raise_error(StandardError)
14
+ end
15
+ end
16
+
17
+ context "without discount_type:" do
18
+ before { data.delete :discount_type }
19
+
20
+ it "fails" do
21
+ expect { subject }.to raise_error(StandardError)
22
+ end
23
+ end
24
+
25
+ context "without discount:" do
26
+ before { data.delete :discount }
27
+
28
+ it "fails" do
29
+ expect { subject }.to raise_error(StandardError)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ RSpec.describe ABBYY::Cloud::Models::Locale do
2
+ it "accepts simple language locales" do
3
+ %w(en sjp yds).each do |locale|
4
+ expect(described_class[locale]).to eq locale
5
+ end
6
+ end
7
+
8
+ it "accepts scripted locales" do
9
+ %w(ar-Aran).each do |locale|
10
+ expect(described_class[locale]).to eq locale
11
+ end
12
+ end
13
+
14
+ it "accepts regional locales" do
15
+ %w(en-GB igb-AA en-018).each do |locale|
16
+ expect(described_class[locale]).to eq locale
17
+ end
18
+ end
19
+
20
+ it "accepts scripted regional locales" do
21
+ %w(ar-Aran-YE ar-Aran-002).each do |locale|
22
+ expect(described_class[locale]).to eq locale
23
+ end
24
+ end
25
+
26
+ it "accepts scripted regional subtagged locales" do
27
+ %w(fr-Aran-FR-1606nict de-1996 de-DE-1996).each do |locale|
28
+ expect(described_class[locale]).to eq locale
29
+ end
30
+ end
31
+
32
+ it "accepts grandfarthered locales" do
33
+ %w(cel-gaulish i-ami en-GB-oed sgn-BE-NL).each do |locale|
34
+ expect(described_class[locale]).to eq locale
35
+ end
36
+ end
37
+
38
+ it "accepts redundant locales" do
39
+ %w(bs-Latn de-AT-1901 zh-Hans-TW).each do |locale|
40
+ expect(described_class[locale]).to eq locale
41
+ end
42
+ end
43
+
44
+ it "accepts private-use locales" do
45
+ %w(en-x-it).each do |locale|
46
+ expect(described_class[locale]).to eq locale
47
+ end
48
+ end
49
+
50
+ it "declines locales that mismatch IANA/IETF standards" do
51
+ %w(i en_EN).each do |locale|
52
+ expect { described_class[locale] }.to raise_error(StandardError)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,107 @@
1
+ RSpec.describe ABBYY::Cloud::Models::Price do
2
+ let(:data) do
3
+ {
4
+ id: "foo",
5
+ account_id: "bar",
6
+ type: "qux",
7
+ from: "ru",
8
+ to: "en",
9
+ unit_prices: [{ unit_type: "Words", currency: "USD", amount: 0.03 }],
10
+ discounts: [{ discount_type: "TMTextMatch", discount: 0.01 }],
11
+ created: Time.now
12
+ }
13
+ end
14
+
15
+ subject { described_class.new(data) }
16
+
17
+ it { is_expected.to be_kind_of ABBYY::Cloud::Struct }
18
+ its(:to_h) { is_expected.to eq data }
19
+
20
+ context "without id:" do
21
+ before { data.delete :id }
22
+
23
+ it "fails" do
24
+ expect { subject }.to raise_error(StandardError)
25
+ end
26
+ end
27
+
28
+ context "without account_id:" do
29
+ before { data.delete :account_id }
30
+
31
+ it "fails" do
32
+ expect { subject }.to raise_error(StandardError)
33
+ end
34
+ end
35
+
36
+ context "without type:" do
37
+ before { data.delete :type }
38
+
39
+ it "fails" do
40
+ expect { subject }.to raise_error(StandardError)
41
+ end
42
+ end
43
+
44
+ context "without source language:" do
45
+ before { data.delete :from }
46
+
47
+ it "fails" do
48
+ expect { subject }.to raise_error(StandardError)
49
+ end
50
+ end
51
+
52
+ context "without target language:" do
53
+ before { data.delete :to }
54
+
55
+ it "fails" do
56
+ expect { subject }.to raise_error(StandardError)
57
+ end
58
+ end
59
+
60
+ context "with invalid unit_prices:" do
61
+ before { data[:unit_prices] = "foo" }
62
+
63
+ it "fails" do
64
+ expect { subject }.to raise_error(StandardError)
65
+ end
66
+ end
67
+
68
+ context "with invalid unit_price:" do
69
+ before { data[:unit_prices] = [{ id: "foo" }] }
70
+
71
+ it "fails" do
72
+ expect { subject }.to raise_error(StandardError)
73
+ end
74
+ end
75
+
76
+ context "without unit_prices:" do
77
+ before { data.delete :unit_prices }
78
+
79
+ it "fails" do
80
+ expect { subject }.to raise_error(StandardError)
81
+ end
82
+ end
83
+
84
+ context "with invalid discounts:" do
85
+ before { data[:discounts] = "foo" }
86
+
87
+ it "fails" do
88
+ expect { subject }.to raise_error(StandardError)
89
+ end
90
+ end
91
+
92
+ context "with invalid discount:" do
93
+ before { data[:discounts] = [{ id: "foo" }] }
94
+
95
+ it "fails" do
96
+ expect { subject }.to raise_error(StandardError)
97
+ end
98
+ end
99
+
100
+ context "without discounts:" do
101
+ before { data.delete :discounts }
102
+
103
+ it "fails" do
104
+ expect { subject }.to raise_error(StandardError)
105
+ end
106
+ end
107
+ end