symgate 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.
@@ -0,0 +1,72 @@
1
+ require 'symgate/client'
2
+ require 'symgate/error'
3
+ require 'symgate/metadata/data_item'
4
+
5
+ # rubocop:disable Style/AccessorMethodName
6
+
7
+ module Symgate
8
+ module Metadata
9
+ # client for the Symgate metadata system
10
+ class Client < Symgate::Client
11
+ # Gets metadata visible to the current user.
12
+ #
13
+ # If the 'scope' option is specified, this will list only the metadata
14
+ # defined at a particular scope. Otherwise it will list metadata items
15
+ # visible, with user metadata replacing group metadata and so on.
16
+ #
17
+ # If the 'keys' option is specified, this will list only the metadata
18
+ # matching the list of keys supplied.
19
+ #
20
+ # The 'key' option is also provided as a convenience, which works as above
21
+ # for a single item.
22
+ def get_metadata(opts = {})
23
+ o = opts
24
+ parse_get_metadata_opts o
25
+ o[:key] = o.delete(:keys) if o.include? :keys
26
+
27
+ resp = savon_request(:get_metadata) { |soap| soap.message(o) }
28
+
29
+ Symgate::Client.savon_array(
30
+ resp.body[:get_metadata_response],
31
+ :data_item,
32
+ Symgate::Metadata::DataItem
33
+ )
34
+ end
35
+
36
+ # Creates one or more metadata items, overwriting any that match the key
37
+ # and scope specified within the DataItem object. Supply either a single
38
+ # item or an array of items.
39
+ def set_metadata(items)
40
+ i = [items].flatten
41
+
42
+ check_array_for_type(i, Symgate::Metadata::DataItem)
43
+ raise Symgate::Error, 'No items supplied' if i.empty?
44
+
45
+ savon_request(:set_metadata, returns_error_string: true) do |soap|
46
+ soap.message('auth:data_item': i.map(&:to_soap))
47
+ end
48
+ end
49
+
50
+ # Destroys one or more metadata items on the specified scope, specified by
51
+ # their key(s). Specify a valid scope and a single string, or an array of
52
+ # strings.
53
+ def destroy_metadata(scope, keys)
54
+ k = [keys].flatten
55
+ check_array_for_type(k, String)
56
+ raise Symgate::Error, 'No keys supplied' if k.empty?
57
+
58
+ savon_request(:destroy_metadata, returns_error_string: true) do |soap|
59
+ soap.message(scope: scope, key: k)
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def parse_get_metadata_opts(opts)
66
+ arrayize_option(:key, :keys, opts)
67
+ check_option_is_array_of(String, :keys, opts)
68
+ check_for_unknown_opts(%i(keys scope), opts)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,34 @@
1
+ require 'symgate/type'
2
+
3
+ module Symgate
4
+ module Metadata
5
+ # meta data item
6
+ class DataItem < Symgate::Type
7
+ def self.from_soap(hash)
8
+ Symgate::Metadata::DataItem.new(
9
+ key: hash[:@key],
10
+ scope: hash[:@scope],
11
+ value: hash_value_with_optional_namespace(:auth, :value, hash)
12
+ )
13
+ end
14
+
15
+ def to_soap
16
+ {
17
+ '@key': key,
18
+ '@scope': scope,
19
+ 'auth:value': value
20
+ }
21
+ end
22
+
23
+ def to_s
24
+ "{DataItem (scope: #{scope}, key #{key}, value #{value})}"
25
+ end
26
+
27
+ protected
28
+
29
+ def attributes
30
+ %i(key value scope)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ require 'symgate/metadata/data_item'
2
+ require 'symgate/metadata/client'
@@ -0,0 +1,9 @@
1
+ module Symgate
2
+ NAMESPACES = {
3
+ 'xmlns:auth': 'http://ws.widgit.com/schema/auth/1.2',
4
+ 'xmlns:wl': 'http://ws.widgit.com/schema/wordlist/1.2',
5
+ 'xmlns:cml': 'http://ws.widgit.com/schema/cml/2.0',
6
+ 'xmlns:gt': 'http://ws.widgit.com/schema/globaltypes/1.2',
7
+ 'xmlns:symboliser': 'http://ws.widgit.com/schema/symboliser/1.2'
8
+ }.freeze
9
+ end
@@ -0,0 +1,55 @@
1
+ require 'symgate/error'
2
+
3
+ module Symgate
4
+ # base class for API types, that provides magic initialisation from hash plus
5
+ # assignment and comparison operators
6
+ class Type
7
+ def initialize(opts = {})
8
+ attrs = attributes
9
+ self.class.class_eval { attr_accessor(*attrs) }
10
+ opts.each do |key, _value|
11
+ unless attributes.include? key
12
+ raise Symgate::Error, "Unknown option #{key} for #{self.class.name}"
13
+ end
14
+ end
15
+
16
+ attributes.each do |attribute|
17
+ instance_variable_set "@#{attribute}", opts[attribute]
18
+ end
19
+ end
20
+
21
+ def ==(other)
22
+ attributes.all? do |attribute|
23
+ a = other.instance_variable_get("@#{attribute}")
24
+ b = instance_variable_get("@#{attribute}")
25
+
26
+ a == b || values_are_empty([a, b])
27
+ end
28
+ end
29
+
30
+ def self.hash_value_with_optional_namespace(namespace, key, hash)
31
+ hash[key] || hash["#{namespace}:#{key}".to_sym]
32
+ end
33
+
34
+ protected
35
+
36
+ # override this to return an array of symbols for your class variables
37
+ # :nocov:
38
+ def attributes
39
+ raise Symgate::Error, "No attributes defined for object type #{self.class.name}"
40
+ end
41
+ # :nocov:
42
+
43
+ def value_or_nil(value)
44
+ if value.respond_to?(:empty?) ? value.empty? : !value
45
+ nil
46
+ else
47
+ value
48
+ end
49
+ end
50
+
51
+ def values_are_empty(values)
52
+ values.all? { |v| value_or_nil(v).nil? }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module Symgate
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,166 @@
1
+ require 'base64'
2
+ require 'symgate/client'
3
+ require 'symgate/error'
4
+ require 'symgate/wordlist/info'
5
+
6
+ module Symgate
7
+ module Wordlist
8
+ # client for the Symgate wordlist system
9
+ class Client < Symgate::Client
10
+ # returns a list of wordlists available to the current user.
11
+ # optionally, supply one or more wordlist contexts as a string or string array parameter
12
+ # to only retrieve wordlist information about that context. e.g.:
13
+ # enumerate_wordlists('User')
14
+ # enumerate_wordlists(%w(Topic SymbolSet))
15
+ def enumerate_wordlists(context = [])
16
+ response = savon_request(:enumerate_wordlists) do |soap|
17
+ soap.message(context: [context].flatten)
18
+ end
19
+
20
+ Symgate::Client.savon_array(
21
+ response.body[:enumerate_wordlists_response],
22
+ :wordlistinfo,
23
+ Symgate::Wordlist::Info
24
+ )
25
+ end
26
+
27
+ # creates a wordlist with the specified name, context and scope (see auth:scope).
28
+ # optionally, supply a list of entries to form the wordlist's initial content
29
+ def create_wordlist(name, context, entries = [])
30
+ tries ||= 3 # TODO: Find out if we still need to do this!
31
+
32
+ Symgate::Wordlist::Info.from_soap(
33
+ savon_request(:create_wordlist) do |soap|
34
+ soap.message(soap_params_for_create_wordlist(name, context, entries))
35
+ end.body[:create_wordlist_response][:wordlistinfo]
36
+ )
37
+ rescue Symgate::Error => e
38
+ # Handle SOS-105 (sometimes the wordlist is created but the symboliser claims it can't
39
+ # find it and sends a SOAP error back) by extract the wordlist UUID from the error message.
40
+ # Yes, this is not nice.
41
+ match = /^No wordlist found for ID: ({[0-9a-f-]{36}})$/.match(e.detail)
42
+
43
+ return get_wordlist_info(match[1]) if match
44
+ (tries -= 1).zero? ? raise(e) : retry
45
+ end
46
+
47
+ # destroys a wordlist with the specified uuid. throws an error on failure
48
+ def destroy_wordlist(uuid)
49
+ tries ||= 3 # TODO: Find out if we still need to do this!
50
+
51
+ savon_request(:destroy_wordlist, returns_error_string: true) do |soap|
52
+ soap.message(wordlistid: uuid)
53
+ end
54
+ rescue Symgate::Error => e
55
+ (tries -= 1).zero? ? raise(e) : retry
56
+ end
57
+
58
+ # returns the information for the wordlist identified by the specified uuid
59
+ def get_wordlist_info(uuid)
60
+ response = savon_request(:get_wordlist_info) do |soap|
61
+ soap.message(wordlistid: uuid)
62
+ end
63
+
64
+ Symgate::Wordlist::Info.from_soap(
65
+ response.body[:get_wordlist_info_response][:wordlistinfo]
66
+ )
67
+ end
68
+
69
+ # returns all wordlist entries for the wordlist specified by uuid.
70
+ # accepts the following optional parameters:
71
+ # attachments (boolean) - fetch custom graphics for the entries (default: false)
72
+ # match (string) - only return wordlist entries matching this word
73
+ # entry (string, uuid) - return the entry specified by the uuid
74
+ def get_wordlist_entries(uuid, opts = {})
75
+ check_for_unknown_opts(%i(match entry attachments), opts)
76
+ check_for_multiple_opts(%i(match entry), opts)
77
+
78
+ response = savon_request(:get_wordlist_entries) do |soap|
79
+ soap.message(wordlistid: uuid, getattachments: opts[:attachments])
80
+ soap[:message][:match] = { matchstring: opts[:match] } if opts.include? :match
81
+ soap[:message][:match] = { entryid: opts[:entry] } if opts.include? :entry
82
+ end
83
+
84
+ Symgate::Client.savon_array(response.body[:get_wordlist_entries_response],
85
+ :wordlistentry,
86
+ Symgate::Wordlist::Entry)
87
+ end
88
+
89
+ # inserts an entry into a wordlist, specified by the wordlist uuid
90
+ def insert_wordlist_entry(uuid, entry)
91
+ unless entry.is_a? Symgate::Wordlist::Entry
92
+ raise Symgate::Error, 'Please supply a Symgate::Wordlist::Entry to insert'
93
+ end
94
+
95
+ savon_request(:insert_wordlist_entry, returns_error_string: true) do |soap|
96
+ soap.message(wordlistid: uuid, 'wl:wordlistentry': entry.to_soap)
97
+ end
98
+ end
99
+
100
+ # overwrites a wordlist with the wordlist contents specified by 'entries'
101
+ def overwrite_wordlist(uuid, entries)
102
+ check_array_for_type(entries, Symgate::Wordlist::Entry)
103
+
104
+ savon_request(:overwrite_wordlist, returns_error_string: true) do |soap|
105
+ soap.message(wordlistid: uuid, 'wl:wordlistentry': entries.map(&:to_soap))
106
+ end
107
+ end
108
+
109
+ # removes the wordlist entry specified by entry_uuid, from the wordlist specified by uuid
110
+ def remove_wordlist_entry(uuid, entry_uuid)
111
+ savon_request(:remove_wordlist_entry, returns_error_string: true) do |soap|
112
+ soap.message(wordlistid: uuid, entryid: entry_uuid)
113
+ end
114
+ end
115
+
116
+ # renames the wordlist specified by its uuid, to the name requested
117
+ def rename_wordlist(uuid, name)
118
+ savon_request(:rename_wordlist, returns_error_string: true) do |soap|
119
+ soap.message(wordlistid: uuid, name: name)
120
+ end
121
+ end
122
+
123
+ # gets the cfwl-format data for the specified wordlist
124
+ def get_wordlist_as_cfwl_data(uuid)
125
+ Base64.decode64(
126
+ savon_request(:get_wordlist_as_cfwl_data) do |soap|
127
+ soap.message(wordlistid: uuid)
128
+ end.body[:get_wordlist_as_cfwl_data_response][:cfwl]
129
+ )
130
+ end
131
+
132
+ # creates a wordlist from the supplied cfwl data, in the requested context
133
+ # if 'preserve_uuid' is true, the new wordlist will have the same uuid as the file
134
+ def create_wordlist_from_cfwl_data(raw_cfwl_data, context, preserve_uuid)
135
+ savon_request(:create_wordlist_from_cfwl_data) do |soap|
136
+ soap.message(cfwl: Base64.encode64(raw_cfwl_data),
137
+ context: context,
138
+ preserve_uuid: preserve_uuid)
139
+ end.body[:create_wordlist_from_cfwl_data_response][:uuid]
140
+ end
141
+
142
+ private
143
+
144
+ def scope_for_context(context)
145
+ case context.to_s
146
+ when 'Topic', 'SymbolSet'
147
+ 'Group'
148
+ when 'Lexical'
149
+ 'Account'
150
+ else
151
+ 'User'
152
+ end
153
+ end
154
+
155
+ def soap_params_for_create_wordlist(name, context, entries)
156
+ {
157
+ name: name,
158
+ context: context,
159
+ scope: scope_for_context(context)
160
+ }.merge(
161
+ entries ? { 'wl:wordlistentry': entries.map(&:to_soap) } : {}
162
+ )
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,48 @@
1
+ require 'symgate/cml/symbol'
2
+ require 'symgate/wordlist/graphic_attachment'
3
+ require 'symgate/type'
4
+ require 'symgate/client'
5
+ require 'tryit'
6
+
7
+ module Symgate
8
+ module Wordlist
9
+ # a wordlist entry
10
+ class Entry < Symgate::Type
11
+ def self.from_soap(hash)
12
+ Symgate::Wordlist::Entry.new(
13
+ word: hash_value_with_optional_namespace(:wl, :word, hash),
14
+ uuid: hash_value_with_optional_namespace(:wl, :uuid, hash),
15
+ priority: hash_value_with_optional_namespace(:wl, :priority, hash).to_i,
16
+ concept_code: hash_value_with_optional_namespace(:wl, :conceptcode, hash),
17
+ symbols: Symgate::Client.savon_array(hash, :symbol,
18
+ Symgate::Cml::Symbol),
19
+ custom_graphics: Symgate::Client.savon_array(hash, :customgraphic,
20
+ Symgate::Wordlist::GraphicAttachment),
21
+ last_change: hash_value_with_optional_namespace(:wl, :lastchange, hash)
22
+ )
23
+ end
24
+
25
+ def to_soap
26
+ {
27
+ 'wl:word': word,
28
+ 'wl:uuid': uuid,
29
+ 'wl:priority': priority,
30
+ 'wl:conceptcode': value_or_nil(concept_code),
31
+ 'cml:symbol': @symbols.tryit { map(&:to_soap) },
32
+ 'wl:customgraphic': @custom_graphics.tryit { map(&:to_soap) },
33
+ 'wl:lastchange': last_change.to_s
34
+ }.delete_if { |_, v| v.nil? }
35
+ end
36
+
37
+ def to_s
38
+ "{Entry: #{@word}[#{@priority}]/#{@uuid} (#{@symbols.count}+#{@custom_graphics.count})}"
39
+ end
40
+
41
+ protected
42
+
43
+ def attributes
44
+ %i(word uuid priority concept_code symbols custom_graphics last_change)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ require 'symgate/type'
2
+ require 'base64'
3
+
4
+ module Symgate
5
+ module Wordlist
6
+ # contains an embedded wordlist entry graphic
7
+ class GraphicAttachment < Symgate::Type
8
+ def self.from_soap(hash)
9
+ data = hash_value_with_optional_namespace(:wl, :data, hash)
10
+
11
+ Symgate::Wordlist::GraphicAttachment.new(
12
+ type: hash_value_with_optional_namespace(:wl, :type, hash),
13
+ uuid: hash_value_with_optional_namespace(:wl, :uuid, hash),
14
+ data: data ? Base64.decode64(data) : nil
15
+ )
16
+ end
17
+
18
+ def to_soap
19
+ {
20
+ 'wl:type': @type,
21
+ 'wl:uuid': @uuid,
22
+ 'wl:data': @data ? Base64.encode64(@data) : nil
23
+ }
24
+ end
25
+
26
+ def to_s
27
+ "{#{@type} Attachment: #{@uuid} (#{@data.length} bytes)}"
28
+ end
29
+
30
+ protected
31
+
32
+ def attributes
33
+ %i(type uuid data)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ require 'symgate/type'
2
+
3
+ module Symgate
4
+ module Wordlist
5
+ # contains information about a wordlist
6
+ class Info < Symgate::Type
7
+ def self.from_soap(hash)
8
+ Symgate::Wordlist::Info.new(
9
+ name: hash_value_with_optional_namespace(:wl, :name, hash),
10
+ context: hash_value_with_optional_namespace(:wl, :context, hash),
11
+ uuid: hash_value_with_optional_namespace(:wl, :uuid, hash),
12
+ engine: hash_value_with_optional_namespace(:wl, :engine, hash),
13
+ scope: hash_value_with_optional_namespace(:wl, :scope, hash),
14
+ entry_count: hash_value_with_optional_namespace(:wl, :entrycount, hash).to_i,
15
+ last_change: hash_value_with_optional_namespace(:wl, :lastchange, hash)
16
+ )
17
+ end
18
+
19
+ def to_s
20
+ "{#{@context} Wordlist: \"#{@name}\"/#{@uuid} (#{@engine}, #{@entry_count} entries)}"
21
+ end
22
+
23
+ protected
24
+
25
+ def attributes
26
+ %i(name context uuid entry_count last_change engine scope)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ require 'symgate/wordlist/info'
2
+ require 'symgate/wordlist/entry'
3
+ require 'symgate/wordlist/graphic_attachment'
4
+ require 'symgate/wordlist/client'
data/lib/symgate.rb ADDED
@@ -0,0 +1 @@
1
+ require 'symgate/version'
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'symgate'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'symgate'
8
+ spec.version = Symgate::VERSION
9
+ spec.authors = ['Simon Detheridge']
10
+ spec.email = ['simon@widgit.com']
11
+
12
+ spec.summary = "Wrapper around Widgit's Symgate SOAP symboliser"
13
+ spec.homepage = 'https://github.com/symbols-worldwide/symgate-gem'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ }
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.9'
22
+ spec.add_development_dependency 'rake', '~> 10.0'
23
+ spec.add_development_dependency 'rspec', '~> 3.4.0'
24
+ spec.add_development_dependency 'rubocop', '~> 0.41.1'
25
+ spec.add_development_dependency 'rspec_junit_formatter', '~> 0.2.3'
26
+ spec.add_development_dependency 'rubocop-junit-formatter', '~> 0.1.3'
27
+ spec.add_development_dependency 'rack', '~> 1.6.4'
28
+ spec.add_development_dependency 'mysql2', '~> 0.4.1'
29
+ spec.add_development_dependency 'simplecov', '~> 0.12.0'
30
+ spec.add_development_dependency 'simplecov-teamcity-summary', '~> 0.1.2'
31
+
32
+ spec.add_dependency 'savon', '~> 2.11.0'
33
+ spec.add_dependency 'tryit', '~> 0.0.1'
34
+ end