unimatrix-activist 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a3591cde5aa039445f5a07e48b3fc9818d9aed0a
4
+ data.tar.gz: abd4244b737b74776b35ff975f1e18144188c88e
5
+ SHA512:
6
+ metadata.gz: b31c75f458bd14949e9ef6b434b7931b853c4044686ea6e1a9b33d85e3ff0cfeffd144f86bf33211bceee3b1fd07520a73609dc8a7d80f770b0d921488875e8b
7
+ data.tar.gz: 608b86df4e8fbf05bcbfdb44281c4098c2939e97cb8eff47082c9f9fcba4eaa1d8e958707b5531ed24781ebed0a344e97a50e59eb52cf1e21d7958b584ba7e1e
@@ -0,0 +1,21 @@
1
+ require 'active_support'
2
+ require 'active_support/all'
3
+ require 'fnv'
4
+
5
+ require 'unimatrix/activist/version'
6
+
7
+ require 'unimatrix/activist/configuration'
8
+ require 'unimatrix/activist/operation'
9
+ require 'unimatrix/activist/request'
10
+ require 'unimatrix/activist/response'
11
+ require 'unimatrix/activist/parser'
12
+ require 'unimatrix/activist/serializer'
13
+
14
+ require 'unimatrix/activist/resources/base'
15
+ require 'unimatrix/activist/resources/error'
16
+ require 'unimatrix/activist/resources/activity'
17
+ require 'unimatrix/activist/resources/task'
18
+
19
+ require 'unimatrix/activist/resources/stream_clip_creation_activity'
20
+ require 'unimatrix/activist/resources/stream_clip_creation_task'
21
+ require 'unimatrix/activist/resources/video_ingestion_task'
@@ -0,0 +1,32 @@
1
+ require 'singleton'
2
+
3
+ module Unimatrix::Activist
4
+
5
+ def self.configuration( &block )
6
+ Configuration.instance().instance_eval( &block ) unless block.nil?
7
+ Configuration.instance()
8
+ end
9
+
10
+ class Configuration
11
+ include Singleton
12
+
13
+ def self.field( field_name, options={} )
14
+ class_eval(
15
+ "def #{ field_name }( *arguments ); " +
16
+ "@#{ field_name } = arguments.first unless arguments.empty?; " +
17
+ "@#{ field_name } || " +
18
+ ( options[ :default ].nil? ?
19
+ "nil" :
20
+ ( options[ :default ].is_a?( String ) ?
21
+ "'#{ options[ :default ] }'" :
22
+ "#{ options[ :default ] }" ) ) + ";" +
23
+ "end",
24
+ __FILE__,
25
+ __LINE__
26
+ )
27
+ end
28
+
29
+ field :url, default: ENV[ 'ACTIVIST_URL' ]
30
+ end
31
+
32
+ end
@@ -0,0 +1,143 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Operation
4
+
5
+ def initialize( path, parameters = {} )
6
+ @path = path
7
+ @parameters = ( parameters || {} ).deep_dup
8
+ @key = nil
9
+ end
10
+
11
+ def key
12
+ return @key ||= begin
13
+ result = 0
14
+ query = @parameters.to_param
15
+ if ( @path.present? || @query.present? )
16
+ query = query.split( '&' ).sort.join( '&' )
17
+ addressable = Addressable::URI.new
18
+ addressable.path = @path
19
+ addressable.query = query unless query.blank?
20
+ result = FNV.new.fnv1a_32( addressable.to_s )
21
+ end
22
+ result
23
+ end
24
+ end
25
+
26
+ def where( parameters )
27
+ self.spawn( parameters )
28
+ end
29
+
30
+ def destroy
31
+ result = nil
32
+ Request.new.tap do | request |
33
+ response = request.destroy( @path, @parameters )
34
+ if response.present?
35
+ result = response.resources
36
+ end
37
+ end
38
+ result
39
+ end
40
+
41
+ def limit( _limit )
42
+ self.spawn( count: _limit )
43
+ end
44
+
45
+ def offset( _offset )
46
+ self.spawn( offset: _offset )
47
+ end
48
+
49
+ def include( *arguments )
50
+ self.spawn( :include => self.normalize_include( *arguments ) )
51
+ end
52
+
53
+ def query( &block )
54
+ result = nil
55
+ Request.new.tap do | request |
56
+ request.get( @path, @parameters ).tap do | response |
57
+ result = response.resources
58
+ if block_given?
59
+ case block.arity
60
+ when 0; yield
61
+ when 1; yield result
62
+ when 2; yield result, response
63
+ end
64
+ end
65
+ end
66
+ end
67
+ result
68
+ end
69
+
70
+ def read( &block )
71
+ response = nil
72
+ result = nil
73
+ self.query do | _result, _response |
74
+ result = _result
75
+ response = _response
76
+ end
77
+ if response.success?
78
+ result = result if result.present? && result.is_a?( Array )
79
+ if block_given?
80
+ case block.arity
81
+ when 0; yield
82
+ when 1; yield result
83
+ when 2; yield result, response
84
+ end
85
+ end
86
+ end
87
+ result
88
+ end
89
+
90
+ def write( node, objects, &block )
91
+ result = nil
92
+ Request.new.tap do | request |
93
+ serializer = Unimatrix::Activist::Serializer.new( objects )
94
+ response = request.post( @path, @parameters, serializer.serialize( node ) )
95
+ if response.present?
96
+ result = response.resources
97
+ if block_given?
98
+ case block.arity
99
+ when 0; yield
100
+ when 1; yield result
101
+ when 2; yield result, response
102
+ end
103
+ end
104
+ end
105
+ end
106
+ result
107
+ end
108
+
109
+ protected; def spawn( parameters )
110
+ Operation.new(
111
+ @path,
112
+ @parameters.deep_merge( parameters || {} )
113
+ )
114
+ end
115
+
116
+ protected; def normalize_include( *arguments )
117
+
118
+ includes = {}
119
+ arguments.each do | argument |
120
+ case argument
121
+ when Array
122
+ argument.each do | value |
123
+ includes.deep_merge!( self.normalize_include( value ) )
124
+ end
125
+ when Hash
126
+ argument.each do | key, value |
127
+ if !includes.include?( key ) || includes[ key ] === true
128
+ includes[ key ] = self.normalize_include( value )
129
+ else
130
+ includes[ key ].deep_merge!( self.normalize_include( value ) )
131
+ end
132
+ end
133
+ else
134
+ includes[ argument ] = true
135
+ end
136
+ end
137
+ includes
138
+
139
+ end
140
+
141
+ end
142
+
143
+ end
@@ -0,0 +1,157 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Parser
4
+
5
+ def initialize( content = {} )
6
+ @content = content
7
+ yield self if block_given?
8
+ end
9
+
10
+ def name
11
+ @content.include?( '$this' ) ?
12
+ @content[ '$this' ][ 'name' ] :
13
+ nil
14
+ end
15
+
16
+ def type_name
17
+ @content.include?( '$this' ) ?
18
+ @content[ '$this' ][ 'type_name' ] :
19
+ nil
20
+ end
21
+
22
+ def key
23
+ 'id'
24
+ end
25
+
26
+ def keys
27
+ @content.include?( '$this' ) ?
28
+ @content[ '$this' ][ 'ids' ] :
29
+ nil
30
+ end
31
+
32
+ def associations
33
+ @content.include?( '$associations' ) ?
34
+ @content[ '$associations' ] :
35
+ nil
36
+ end
37
+
38
+ def resources
39
+ result = nil
40
+
41
+ unless self.name.blank?
42
+ result = self.keys.map do | key |
43
+ self.resource_by( name, key, { 'type_name' => self.type_name } )
44
+ end
45
+ end
46
+ result
47
+ end
48
+
49
+ def parse_resource( name, attributes )
50
+ @resources_mutex ||= Hash.new { | hash, key | hash[ key ] = [] }
51
+ object_key = attributes[ key ]
52
+
53
+ # Lock the resource index for this name/key combination
54
+ # This prevents objects that are associated with
55
+ # themselves from causing a stack overflow
56
+ return nil if @resources_mutex[ name ].include?( object_key )
57
+
58
+ @resources_mutex[ name ].push( object_key )
59
+ resource = nil
60
+
61
+ if attributes.present?
62
+ class_name = name.singularize.camelize
63
+ object_class = Unimatrix::Activist.const_get( class_name ) rescue nil
64
+
65
+ if object_class.present?
66
+ relations = name == self.name ?
67
+ self.parse_associations( attributes ) : []
68
+ resource = object_class.new( attributes, relations )
69
+ end
70
+ @resources_mutex[ name ].delete( object_key )
71
+ end
72
+ resource
73
+ end
74
+
75
+ def resource_by( name, key, options = {} )
76
+
77
+ @resources_index ||= Hash.new { | hash, key | hash[ key ] = {} }
78
+ @resource_index_mutex ||= Hash.new { | hash, key | hash[ key ] = [] }
79
+
80
+ @resources_index[ name ][ key ] ||= begin
81
+
82
+ # lock the resource index for this name/key combination
83
+ # note: this prevents Unimatrix::Activist objects that are associated with
84
+ # themselves from causing a stack overflow
85
+ return nil if @resource_index_mutex[ name ].include?( key )
86
+ @resource_index_mutex[ name ].push( key )
87
+
88
+ result = nil
89
+ resource_attributes = resource_attribute_index[ name ][ key ]
90
+ if resource_attributes.present?
91
+ type_name = resource_attributes[ 'type_name' ]
92
+ klass = nil
93
+ klass = ( Unimatrix::Activist.const_get( type_name.camelize ) rescue nil ) \
94
+ if type_name.present?
95
+ if klass.nil?
96
+ type_name = options[ 'type_name' ]
97
+ klass = ( Unimatrix::Activist.const_get( type_name.camelize ) rescue nil ) \
98
+ if type_name.present?
99
+ end
100
+ if klass.present?
101
+ result = klass.new(
102
+ resource_attributes,
103
+ self.resource_associations_by( name, key )
104
+ )
105
+ end
106
+ end
107
+
108
+ # unlock the resource index for this name/key combination
109
+ @resource_index_mutex[ name ].delete( key )
110
+
111
+ result
112
+
113
+ end
114
+
115
+ end
116
+
117
+ def resource_associations_by( name, key )
118
+ result = Hash.new { | hash, key | hash[ key ] = [] }
119
+ associations = self.associations
120
+ if associations && associations.include?( name )
121
+ association = associations[ name ].detect do | association |
122
+ association[ 'id' ] == key
123
+ end
124
+ if association.present?
125
+ association.each do | key, value |
126
+ unless key == 'id'
127
+ type_name = value[ 'type_name' ]
128
+ result[ key ] = ( value[ 'ids' ] || [] ).map do | associated_id |
129
+ self.resource_by(
130
+ key,
131
+ associated_id,
132
+ { 'type_name' => type_name }
133
+ )
134
+ end
135
+ result[ key ].compact!
136
+ end
137
+ end
138
+ end
139
+ end
140
+ result
141
+ end
142
+
143
+ def resource_attribute_index
144
+ @resource_attribute_index ||= begin
145
+ index = Hash.new { | hash, key | hash[ key ] = {} }
146
+ @content.each do | key, resources_attributes |
147
+ unless key[0] == '$'
148
+ resources_attributes.each do | resource_attributes |
149
+ index[ key ][ resource_attributes[ 'id' ] ] = resource_attributes
150
+ end
151
+ end
152
+ end
153
+ index
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,62 @@
1
+ require 'net/http'
2
+ require 'addressable/uri'
3
+
4
+ module Unimatrix::Activist
5
+
6
+ class Request
7
+
8
+ def initialize( default_parameters = {} )
9
+ uri = URI( Unimatrix::Activist.configuration.url )
10
+ @http = Net::HTTP.new( uri.host, uri.port )
11
+
12
+ @http.use_ssl = ( uri.scheme == 'https' )
13
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
14
+
15
+ @default_parameters = default_parameters.stringify_keys
16
+ end
17
+
18
+ def get( path, parameters = {} )
19
+ response = nil
20
+
21
+ begin
22
+ response = Response.new(
23
+ @http.get( compose_request_path( path, parameters ) )
24
+ )
25
+ rescue Timeout::Error
26
+ response = nil
27
+ end
28
+
29
+ response
30
+ end
31
+
32
+ def post( path, parameters = {}, body = {} )
33
+ response = nil
34
+
35
+ begin
36
+ request = Net::HTTP::Post.new(
37
+ compose_request_path( path, parameters ),
38
+ { 'Content-Type' =>'application/json' }
39
+ )
40
+ request.body = body.to_json
41
+
42
+ response = Response.new( @http.request( request ) )
43
+ rescue Timeout::Error
44
+ response = nil
45
+ end
46
+
47
+ response
48
+ end
49
+
50
+ protected; def compose_request_path( path, parameters = {} )
51
+ parameters = @default_parameters.merge( parameters.stringify_keys )
52
+ addressable = Addressable::URI.new
53
+
54
+ addressable.path = path
55
+ addressable.query = parameters.to_param unless parameters.blank?
56
+
57
+ addressable.to_s
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,20 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Activity < Base
4
+ field :id
5
+ field :type_name
6
+ field :subject_id
7
+ field :subject_type
8
+ field :state
9
+ field :message
10
+ field :properties
11
+ field :completed_at
12
+ field :destroyed_at
13
+ field :created_at
14
+ field :updated_at
15
+ field :execute_at
16
+
17
+ has_many :task
18
+ end
19
+
20
+ end
@@ -0,0 +1,70 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Base
4
+
5
+ class << self
6
+
7
+ def inherited( subclass )
8
+ subclass.fields = {}.merge( self.fields )
9
+ end
10
+
11
+ def field( name, options = {} )
12
+ self.fields[ name.to_sym ] = options.merge( name: name )
13
+
14
+ class_eval(
15
+ "def #{ name }(); " +
16
+ "@#{ name }.is_a?( FalseClass ) ? @#{ name } : (" +
17
+ "@#{ name } || " +
18
+ ( options[ :default ].nil? ?
19
+ "nil" :
20
+ ( options[ :default ].is_a?( String ) ?
21
+ "'#{ options[ :default ] }'" :
22
+ "#{ options[ :default ] }" ) ) + ");" +
23
+ "end;" +
24
+ " " +
25
+ "attr_writer :#{ name };",
26
+ __FILE__,
27
+ __LINE__
28
+ )
29
+
30
+ end
31
+
32
+ def has_one( name, options = {} )
33
+ define_method name do
34
+ associations = self.instance_variable_get( "@_#{ name.to_s.pluralize }" )
35
+ associations.present? ? associations.first : options[ :default ]
36
+ end
37
+ end
38
+
39
+ def has_many( name, options = {} )
40
+ define_method name do
41
+ self.instance_variable_get( "@_#{ name }" ) ||
42
+ options[ :default ] || []
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ class_attribute :fields, instance_writer: false
49
+ self.fields = {}
50
+
51
+ field :type_name
52
+ has_many :errors
53
+
54
+ def initialize( attributes={}, associations={} )
55
+ self.type_name = self.class.name.gsub( /Unimatrix::Activist::/, '' ).underscore
56
+
57
+ attributes.each do | key, value |
58
+ send( "#{ key }=", value ) if respond_to?( "#{ key }=" )
59
+ end
60
+
61
+ associations.each do | key, value |
62
+ self.instance_variable_set( "@_#{ key }", value )
63
+ end
64
+
65
+ yield self if block_given?
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,8 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Error < Base
4
+ field :code
5
+ field :message
6
+ end
7
+
8
+ end
@@ -0,0 +1,12 @@
1
+ module Unimatrix::Activist
2
+
3
+ class StreamClipCreationActivity < Activity
4
+ field :in_point
5
+ field :out_point
6
+ field :clip_name
7
+ field :original_stream_id
8
+ field :video_file_url
9
+ field :video_id
10
+ end
11
+
12
+ end
@@ -0,0 +1,11 @@
1
+ module Unimatrix::Activist
2
+
3
+ class StreamClipCreationTask < Task
4
+ field :in_point
5
+ field :clip_name
6
+ field :video_file_url
7
+ field :out_point
8
+ field :original_stream_id
9
+ end
10
+
11
+ end
@@ -0,0 +1,21 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Task < Base
4
+ field :id
5
+ field :type_name
6
+ field :subject_id
7
+ field :activity_id
8
+ field :subject_type
9
+ field :state
10
+ field :message
11
+ field :properties
12
+ field :execute_at
13
+ field :started_at
14
+ field :ended_at
15
+ field :created_at
16
+ field :updated_at
17
+
18
+ has_one :activity
19
+ end
20
+
21
+ end
@@ -0,0 +1,9 @@
1
+ module Unimatrix::Activist
2
+
3
+ class VideoIngestionTask < Task
4
+ field :video_id
5
+ field :clip_name
6
+ field :video_file_url
7
+ end
8
+
9
+ end
@@ -0,0 +1,44 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Response
4
+
5
+ attr_reader :code
6
+ attr_reader :body
7
+ attr_reader :resources
8
+
9
+ def initialize( http_response )
10
+ @success = http_response.is_a?( Net::HTTPOK )
11
+ @code = http_response.code
12
+ @resources = []
13
+ @body = decode_response_body( http_response )
14
+
15
+ if ( @body && @body.respond_to?( :keys ) )
16
+ Parser.new( @body ) do | parser |
17
+ @resources = parser.resources
18
+ @success = !( parser.type_name == 'error' )
19
+ end
20
+ else
21
+ @success = false
22
+ @resources << Error.new(
23
+ message: "#{ @code }: #{ http_response.message }."
24
+ )
25
+ end
26
+ end
27
+
28
+ def success?
29
+ @success
30
+ end
31
+
32
+ protected; def decode_response_body( http_response )
33
+ body = http_response.body
34
+
35
+ if body.present?
36
+ JSON.parse( body ) rescue nil
37
+ else
38
+ nil
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,31 @@
1
+ module Unimatrix::Activist
2
+
3
+ class Serializer
4
+
5
+ def initialize( payload = [], options = {} )
6
+ @payload = [ payload ].flatten
7
+ @options = options
8
+ end
9
+
10
+ def serialize( node, options = {} )
11
+ result = {}
12
+ result[ node ] = @payload.map do | object |
13
+ node_object = {}
14
+ node_object[ :type_name ] = (
15
+ object.respond_to?( :type_name ) ?
16
+ object.type_name :
17
+ object.class.name.gsub( /Unimatrix::Activist/, '' ).underscore
18
+ )
19
+ if object.respond_to?( :fields )
20
+ object.fields.each do | name, options |
21
+ node_object[ name.to_sym ] = object.send( name )
22
+ end
23
+ end
24
+ node_object
25
+ end
26
+ result
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,5 @@
1
+ module Unimatrix
2
+ module Activist
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unimatrix-activist
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Yang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: addressable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fnv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-nav
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: The unimatrix-activist facilitates making requests to the Unimatrix Activist
84
+ application
85
+ email:
86
+ - matthewyang@sportsrocket.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - lib/unimatrix-activist.rb
92
+ - lib/unimatrix/activist/configuration.rb
93
+ - lib/unimatrix/activist/operation.rb
94
+ - lib/unimatrix/activist/parser.rb
95
+ - lib/unimatrix/activist/request.rb
96
+ - lib/unimatrix/activist/resources/activity.rb
97
+ - lib/unimatrix/activist/resources/base.rb
98
+ - lib/unimatrix/activist/resources/error.rb
99
+ - lib/unimatrix/activist/resources/stream_clip_creation_activity.rb
100
+ - lib/unimatrix/activist/resources/stream_clip_creation_task.rb
101
+ - lib/unimatrix/activist/resources/task.rb
102
+ - lib/unimatrix/activist/resources/video_ingestion_task.rb
103
+ - lib/unimatrix/activist/response.rb
104
+ - lib/unimatrix/activist/serializer.rb
105
+ - lib/unimatrix/activist/version.rb
106
+ homepage: http://sportsrocket.com
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.4.6
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Unimatrix::Activist is used to communicate with Activist.
130
+ test_files: []