abbyy-cloud 0.0.3 → 0.0.4

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