opener-s3-outlet 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 +7 -0
- data/README.md +57 -0
- data/bin/s3-outlet-daemon +9 -0
- data/bin/s3-outlet-server +10 -0
- data/config.ru +4 -0
- data/config/aws.rb +8 -0
- data/exec/s3-outlet.rb +33 -0
- data/lib/opener/s3_outlet.rb +39 -0
- data/lib/opener/s3_outlet/public/outlet.css +58 -0
- data/lib/opener/s3_outlet/s3_output.rb +59 -0
- data/lib/opener/s3_outlet/server.rb +76 -0
- data/lib/opener/s3_outlet/version.rb +5 -0
- data/lib/opener/s3_outlet/views/index.erb +33 -0
- data/lib/opener/s3_outlet/views/show.erb +10 -0
- data/lib/opener/s3_outlet/views/show.html.erb +11 -0
- data/lib/opener/s3_outlet/visualizer.rb +278 -0
- data/opener-s3-outlet.gemspec +38 -0
- data/visualizer.rb +278 -0
- metadata +230 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 10341e220c8473c0a1122c20b7ab9dc7b1f8452e
|
4
|
+
data.tar.gz: e4a9dcf12a019669c8879838ff5f94ac3fc09a9b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 975df73d82655fe9b781aed0e485a7805c68f3746442dd226d55217abe9acb411b98ea153174035dd242adf346f436ef49cc9b7b2f9a0a5325cebd727eecd073
|
7
|
+
data.tar.gz: ab71c20499c3bae2a3ca8a6e35710520786275c59d47b3ef49b2bacecfedbf133e9243206f258a14c1e136a0bbcb99befe467f026af76e335c3782fa0cf39360
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# S3-Outlet: Stores the results in an AWS S3 bucket.
|
2
|
+
|
3
|
+
Component that stores results from the Opener Web Services chain into an S3
|
4
|
+
bucket.
|
5
|
+
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
### As part of a Gemfile in a Ruby application
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'opener-s3-outlet',
|
14
|
+
:git=>"git@github.com:opener-project/s3-outlet.git"
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle install
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
The Opener S3 Outlet comes equipped with a simple webservice. To start the
|
23
|
+
webservice type:
|
24
|
+
|
25
|
+
s3-outlet-server
|
26
|
+
|
27
|
+
This will launch a mini webserver with the webservice. It defaults to port 9292,
|
28
|
+
so you can access it at:
|
29
|
+
|
30
|
+
http://localhost:9292
|
31
|
+
|
32
|
+
To launch it on a different port provide the ```-p [port-number]``` option like
|
33
|
+
this:
|
34
|
+
|
35
|
+
s3-outlet-server -p 1234
|
36
|
+
|
37
|
+
It then launches at ```http://localhost:1234```
|
38
|
+
|
39
|
+
When you run a chain of web services using callbacks, the last URL should be the
|
40
|
+
one that points to the S3 Outlet Web Service. A unique id is generated and once the
|
41
|
+
chain has finished processing the text, you can view the result in the URL that
|
42
|
+
you get.
|
43
|
+
|
44
|
+
## Contributing
|
45
|
+
|
46
|
+
### Procedure
|
47
|
+
|
48
|
+
1. Pull it
|
49
|
+
2. Create your feature branch (`git checkout -b features/my-new-feature`)
|
50
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
51
|
+
4. Push to the branch (`git push origin features/my-new-feature`)
|
52
|
+
5. If you're confident, merge your changes into master.
|
53
|
+
|
54
|
+
# What's next?
|
55
|
+
|
56
|
+
If you're interested in the opener-s3-outlet, you also might want to check
|
57
|
+
out opener-project/s3-outlet.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
# Without calling `Rack::Server#options` manually the CLI arguments will never
|
6
|
+
# be passed, thus the application can't be specified as a constructor argument.
|
7
|
+
server = Rack::Server.new
|
8
|
+
server.options[:config] = File.expand_path('../../config.ru', __FILE__)
|
9
|
+
|
10
|
+
server.start
|
data/config.ru
ADDED
data/config/aws.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# These details are tied to the "opener" AWS user.
|
2
|
+
AWS.config(
|
3
|
+
:access_key_id => 'AKIAIZH4THHTIHYS22NA',
|
4
|
+
:secret_access_key => 'BOPSE0KKP19009LgGpOmc43nrh9vs9DKMMBEyix/',
|
5
|
+
:sqs_region => 'eu-west-1',
|
6
|
+
:sqs_endpoint => 'sqs.eu-west-1.amazonaws.com',
|
7
|
+
:s3_endpoint => 's3-eu-west-1.amazonaws.com',
|
8
|
+
)
|
data/exec/s3-outlet.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'opener/daemons'
|
4
|
+
require_relative '../lib/opener/s3_outlet'
|
5
|
+
|
6
|
+
parser = Opener::Daemons::OptParser.new do |opts, options|
|
7
|
+
opts.on("-b", "--bucket NAME", "Bucket name") do |v|
|
8
|
+
options[:bucket] = v
|
9
|
+
end
|
10
|
+
opts.on("-d", "--directory NAME", "Directory name") do |v|
|
11
|
+
options[:directory] = v
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class S3OutletFactory
|
16
|
+
attr_reader :dir, :bucket
|
17
|
+
|
18
|
+
def initialize(options)
|
19
|
+
@bucket = options.fetch(:bucket)
|
20
|
+
@dir = options.fetch(:directory)
|
21
|
+
end
|
22
|
+
|
23
|
+
def new
|
24
|
+
Opener::S3Outlet.new(:directory=>dir, :bucket=>bucket)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
options = parser.parse!(ARGV)
|
29
|
+
factory = S3OutletFactory.new(options)
|
30
|
+
|
31
|
+
daemon = Opener::Daemons::Daemon.new(factory, options)
|
32
|
+
daemon.start
|
33
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
require_relative '../../config/aws'
|
4
|
+
require_relative 's3_outlet/s3_output'
|
5
|
+
require_relative 's3_outlet/version'
|
6
|
+
require_relative 's3_outlet/server'
|
7
|
+
|
8
|
+
module Opener
|
9
|
+
class S3Outlet
|
10
|
+
attr_reader :options
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(input)
|
17
|
+
options[:text] = input
|
18
|
+
S3Output.create(options)
|
19
|
+
|
20
|
+
return input #Return original input so that we can keep on chaining.
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.s3
|
24
|
+
@s3 ||= AWS::S3.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.bucket
|
28
|
+
@bucket ||= s3.buckets[bucket_name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.bucket_name
|
32
|
+
return "opener-outlet"
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.dir
|
36
|
+
return "webservice"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
body {
|
2
|
+
font-family: arial;
|
3
|
+
line-height: 2em;
|
4
|
+
font-size: 15px;
|
5
|
+
margin-top: 3em;
|
6
|
+
}
|
7
|
+
|
8
|
+
.opener {
|
9
|
+
width: 800px;
|
10
|
+
margin-left: auto;
|
11
|
+
margin-right: auto;
|
12
|
+
}
|
13
|
+
|
14
|
+
.entities {
|
15
|
+
margin: 1em;
|
16
|
+
}
|
17
|
+
|
18
|
+
.entities:before {
|
19
|
+
font-size: 16px;
|
20
|
+
font-weight: bold;
|
21
|
+
content: "Entities";
|
22
|
+
}
|
23
|
+
|
24
|
+
.opinions {
|
25
|
+
margin: 1em;
|
26
|
+
}
|
27
|
+
|
28
|
+
.opinions:before {
|
29
|
+
font-size: 16px;
|
30
|
+
font-weight: bold;
|
31
|
+
content: "Opinions";
|
32
|
+
}
|
33
|
+
|
34
|
+
.properties {
|
35
|
+
margin: 1em;
|
36
|
+
}
|
37
|
+
|
38
|
+
.properties:before {
|
39
|
+
font-size: 16px;
|
40
|
+
font-weight: bold;
|
41
|
+
content: "Aspects";
|
42
|
+
}
|
43
|
+
|
44
|
+
.positive {
|
45
|
+
background-color: #AFA;
|
46
|
+
}
|
47
|
+
|
48
|
+
.negative {
|
49
|
+
background-color: #FAA;
|
50
|
+
}
|
51
|
+
|
52
|
+
.entity {
|
53
|
+
border-bottom: 2px dotted #00F;
|
54
|
+
}
|
55
|
+
|
56
|
+
.property {
|
57
|
+
border-bottom: 2px solid orange;
|
58
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'uuidtools'
|
2
|
+
|
3
|
+
module Opener
|
4
|
+
class S3Outlet
|
5
|
+
class S3Output
|
6
|
+
#attr_accessor :params, :bucket
|
7
|
+
attr_reader :uuid, :text, :dir, :bucket, :params
|
8
|
+
|
9
|
+
def initialize(params = {})
|
10
|
+
@uuid = params.fetch(:uuid) { UUIDTools::UUID.random_create }
|
11
|
+
@text = params.fetch(:text)
|
12
|
+
@dir = params.fetch(:directory, S3Outlet.dir)
|
13
|
+
|
14
|
+
bucket = params[:bucket]
|
15
|
+
if bucket.kind_of?(String)
|
16
|
+
@bucket = S3Outlet.s3.buckets[bucket]
|
17
|
+
else
|
18
|
+
@bucket = bucket || S3Outlet.bucket
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def save
|
23
|
+
object = bucket.objects[filename]
|
24
|
+
object.write(text)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.find(uuid, dir=nil, bucket=nil)
|
28
|
+
filename = construct_filename(uuid, dir)
|
29
|
+
bucket = bucket || default_bucket
|
30
|
+
|
31
|
+
if bucket.objects[filename].exists?
|
32
|
+
file = bucket.objects[filename]
|
33
|
+
return file.read
|
34
|
+
else
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.create(params={})
|
40
|
+
new(params).save
|
41
|
+
end
|
42
|
+
|
43
|
+
def filename
|
44
|
+
self.class.construct_filename(uuid, dir)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def self.construct_filename(uuid, dir=nil)
|
50
|
+
dir = S3Outlet.dir if dir.nil? || dir.empty?
|
51
|
+
File.join(dir, "#{uuid}.kaf")
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.default_bucket
|
55
|
+
S3Outlet.bucket
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'stringio'
|
4
|
+
require_relative './visualizer'
|
5
|
+
|
6
|
+
require File.expand_path('../../../../config/aws', __FILE__)
|
7
|
+
|
8
|
+
module Opener
|
9
|
+
class S3Outlet
|
10
|
+
class Server < Sinatra::Base
|
11
|
+
|
12
|
+
post '/' do
|
13
|
+
output = S3Output.create(:uuid => params[:request_id], :text => params[:input])
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/' do
|
17
|
+
if params[:request_id]
|
18
|
+
redirect "#{url("/")}#{params[:request_id]}"
|
19
|
+
else
|
20
|
+
erb :index
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
get '/:request_id' do
|
25
|
+
unless params[:request_id] == 'favicon.ico'
|
26
|
+
begin
|
27
|
+
output = S3Output.find(params[:request_id])
|
28
|
+
|
29
|
+
if output
|
30
|
+
content_type(:xml)
|
31
|
+
body(output)
|
32
|
+
else
|
33
|
+
halt(404, "No record found for ID #{params[:request_id]}")
|
34
|
+
end
|
35
|
+
rescue => error
|
36
|
+
error_callback = params[:error_callback]
|
37
|
+
|
38
|
+
submit_error(error_callback, error.message) if error_callback
|
39
|
+
|
40
|
+
raise(error)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
get '/html/:request_id' do
|
46
|
+
unless params[:request_id] == 'favicon.ico'
|
47
|
+
begin
|
48
|
+
output = S3Output.find(params[:request_id])
|
49
|
+
if output
|
50
|
+
output = StringIO.new(output)
|
51
|
+
parser = Opener::Kaf::Visualizer::Parser.new(output)
|
52
|
+
doc = parser.parse
|
53
|
+
html = Opener::Kaf::Visualizer::HTMLTextPresenter.new(doc)
|
54
|
+
@parsed = html.to_html
|
55
|
+
erb :show
|
56
|
+
else
|
57
|
+
halt(404, "No record found for ID #{params[:request_id]}")
|
58
|
+
end
|
59
|
+
rescue => error
|
60
|
+
error_callback = params[:error_callback]
|
61
|
+
|
62
|
+
submit_error(error_callback, error.message) if error_callback
|
63
|
+
|
64
|
+
raise(error)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def submit_error(url, message)
|
72
|
+
HTTPClient.post(url, :body => {:error => message})
|
73
|
+
end
|
74
|
+
end # Server
|
75
|
+
end # S3Outlet
|
76
|
+
end # Opener
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<link type="text/css" rel="stylesheet" charset="UTF-8" href="markdown.css"/>
|
5
|
+
<title>Opener S3 Outlet</title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1>Opener S3 Outlet</h1>
|
9
|
+
|
10
|
+
<p>This Web Service, stores the output from a chain of Opener components in a
|
11
|
+
S3 bucket.</p>
|
12
|
+
<p>When using callbacks, the last Callback URL should be the URL that points
|
13
|
+
to S3 Outlet. Then, when you submit your text, you get a URL where the result
|
14
|
+
will be shown, when the processing is over.</p>
|
15
|
+
|
16
|
+
<h2>Search for an output</h2>
|
17
|
+
|
18
|
+
<p>* required</p>
|
19
|
+
|
20
|
+
|
21
|
+
<form action="<%=url("/")%>" method="GET">
|
22
|
+
<div>
|
23
|
+
<label for="request_id"/>Type the ID here*</label>
|
24
|
+
<br/>
|
25
|
+
|
26
|
+
<input type="text" name="request_id" id="text" rows="10" cols="50"/>
|
27
|
+
</div>
|
28
|
+
<input type="submit" value="Submit" />
|
29
|
+
</form>
|
30
|
+
|
31
|
+
</body>
|
32
|
+
</html>
|
33
|
+
|
@@ -0,0 +1,278 @@
|
|
1
|
+
#require "opener/kaf/visualizer/version"
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Opener
|
5
|
+
module Kaf
|
6
|
+
module Visualizer
|
7
|
+
class Parser
|
8
|
+
attr_reader :doc
|
9
|
+
attr_reader :words, :terms, :entities, :properties, :opinions, :document
|
10
|
+
|
11
|
+
def initialize(input_file_handler)
|
12
|
+
@doc = Nokogiri::XML(input_file_handler)
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse
|
16
|
+
@words = parse_words
|
17
|
+
@terms = parse_terms
|
18
|
+
@entities = parse_entities
|
19
|
+
@properties = parse_properties
|
20
|
+
@opinions = parse_opinions
|
21
|
+
@document = KAFDocument.new(
|
22
|
+
:words => words,
|
23
|
+
:terms => terms,
|
24
|
+
:entities => entities,
|
25
|
+
:properties => properties,
|
26
|
+
:opinions => opinions
|
27
|
+
)
|
28
|
+
|
29
|
+
return document
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_words
|
33
|
+
parse_elements("//wf", Word)
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_terms
|
37
|
+
# Of course terms should be words here.
|
38
|
+
# Dirty Hack, sufficient for now.
|
39
|
+
parse_elements("//term", Term, :terms=>words)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_entities
|
43
|
+
parse_elements("//entity", Entity, :terms=>terms)
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_properties
|
47
|
+
parse_elements("//property", Property, :terms=>terms)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_opinions
|
51
|
+
parse_elements("//opinion", Opinion, :terms=>terms)
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_elements(xpath, klass, opts={})
|
55
|
+
elements = doc.xpath(xpath)
|
56
|
+
lookup_table = Hash.new
|
57
|
+
elements.each do |element|
|
58
|
+
instance = klass.new(element, opts)
|
59
|
+
lookup_table[instance.id] = instance
|
60
|
+
end
|
61
|
+
lookup_table
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
class KAFNode
|
67
|
+
attr_reader :content, :targets, :tag, :references
|
68
|
+
|
69
|
+
def initialize(tag, references)
|
70
|
+
@references = references
|
71
|
+
@tag = tag
|
72
|
+
|
73
|
+
set_instance_variables
|
74
|
+
set_content
|
75
|
+
set_targets
|
76
|
+
process_subnodes
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_content
|
80
|
+
@content = tag.content
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_instance_variables
|
84
|
+
tag.keys.each do |key|
|
85
|
+
if respond_to?("#{key}=".to_sym)
|
86
|
+
send("#{key}=".to_sym, tag[key])
|
87
|
+
else
|
88
|
+
instance_variable_set("@#{key}", tag[key])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_targets
|
94
|
+
@targets = []
|
95
|
+
tag.css("span target").each do |target|
|
96
|
+
id = target["id"]
|
97
|
+
@targets << references[:terms][id]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def process_subnodes
|
102
|
+
end
|
103
|
+
|
104
|
+
def has_target?(*ids)
|
105
|
+
ids.flatten.each do |id|
|
106
|
+
return true if target_ids.include?(id)
|
107
|
+
end
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_s
|
112
|
+
if targets.size > 0
|
113
|
+
return targets.map(&:to_s).join(" ")
|
114
|
+
else
|
115
|
+
return content
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def target_ids
|
120
|
+
@targets.map(&:id)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
class Word < KAFNode
|
126
|
+
attr_reader :wid, :sent, :para, :offset
|
127
|
+
|
128
|
+
def id
|
129
|
+
wid
|
130
|
+
end
|
131
|
+
|
132
|
+
def offset=(offset)
|
133
|
+
@offset = offset.to_i
|
134
|
+
end
|
135
|
+
|
136
|
+
def length
|
137
|
+
content.nil? ? 0 : content.length
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
class Term < KAFNode
|
143
|
+
attr_reader :tid, :type, :lemma, :pos, :morphofeat
|
144
|
+
|
145
|
+
def id
|
146
|
+
tid
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Entity < KAFNode
|
151
|
+
attr_reader :eid, :type
|
152
|
+
|
153
|
+
def id
|
154
|
+
eid
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_s
|
158
|
+
"#{type}: #{targets.map(&:to_s).join(", ")}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class Property < KAFNode
|
163
|
+
attr_reader :pid, :lemma
|
164
|
+
|
165
|
+
def id
|
166
|
+
pid
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_s
|
170
|
+
"#{lemma}: #{targets.map(&:to_s).join(", ")}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class Opinion < KAFNode
|
175
|
+
attr_reader :oid, :expression
|
176
|
+
|
177
|
+
def id
|
178
|
+
oid
|
179
|
+
end
|
180
|
+
|
181
|
+
def process_subnodes
|
182
|
+
@expression = tag.xpath("opinion_expression").first["polarity"].to_sym
|
183
|
+
end
|
184
|
+
|
185
|
+
def to_s
|
186
|
+
"#{expression}: #{targets.map(&:to_s).join(", ")}"
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
class KAFDocument
|
192
|
+
attr_reader :words, :terms, :entities, :properties, :opinions
|
193
|
+
|
194
|
+
def initialize(opts={})
|
195
|
+
@words = opts.fetch(:words)
|
196
|
+
@terms = opts.fetch(:terms)
|
197
|
+
@entities = opts.fetch(:entities)
|
198
|
+
@properties = opts.fetch(:properties)
|
199
|
+
@opinions = opts.fetch(:opinions)
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
class HTMLTextPresenter
|
205
|
+
attr_reader :document
|
206
|
+
def initialize(document)
|
207
|
+
@document = document
|
208
|
+
end
|
209
|
+
|
210
|
+
def to_html
|
211
|
+
offset = 0
|
212
|
+
prev = Struct.new(:offset).new(0)
|
213
|
+
|
214
|
+
builder = Nokogiri::HTML::Builder.new do |html|
|
215
|
+
html.div(:class=>"opener") do
|
216
|
+
html.p do
|
217
|
+
document.words.values.sort_by(&:offset).each do |word|
|
218
|
+
if offset < word.offset
|
219
|
+
spacer = word.offset - offset
|
220
|
+
spacer = Array.new(spacer, " ").join
|
221
|
+
html.span(spacer)
|
222
|
+
end
|
223
|
+
|
224
|
+
terms = targets_for(:terms, word.id)
|
225
|
+
entities = targets_for(:entities, terms)
|
226
|
+
opinions = opinions_for(terms)
|
227
|
+
properties = targets_for(:properties, terms)
|
228
|
+
|
229
|
+
generics = []
|
230
|
+
generics << "term" if terms.size > 0
|
231
|
+
generics << "entity" if entities.size > 0
|
232
|
+
generics << "opinion" if opinions.size > 0
|
233
|
+
generics << "property" if properties.size > 0
|
234
|
+
|
235
|
+
classes = [terms, entities, opinions, properties, generics].flatten.uniq
|
236
|
+
|
237
|
+
word_annotations = classes.join(" ")
|
238
|
+
|
239
|
+
html.span(word.content, :class=>word_annotations, :id=>word.id)
|
240
|
+
offset = word.offset + word.length
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
[:entities, :opinions, :properties].each do |sym|
|
245
|
+
html.div(:class=>sym) do
|
246
|
+
document.public_send(sym).values.each do |entity|
|
247
|
+
html.div(entity.to_s, :id=>entity.id, :class=>entity.target_ids.join(" "))
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
builder.to_html
|
255
|
+
end
|
256
|
+
|
257
|
+
def targets_for(variable, *ids)
|
258
|
+
targets = document.public_send(variable.to_sym).values.select do |value|
|
259
|
+
value.has_target?(ids.flatten)
|
260
|
+
end
|
261
|
+
|
262
|
+
targets.map(&:id)
|
263
|
+
end
|
264
|
+
|
265
|
+
def opinions_for(*ids)
|
266
|
+
targets = document.opinions.values.select do |value|
|
267
|
+
value.has_target?(ids.flatten)
|
268
|
+
end
|
269
|
+
|
270
|
+
ids = targets.map(&:id)
|
271
|
+
sentiments = targets.map(&:expression)
|
272
|
+
return ids.concat(sentiments).uniq
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path('../lib/opener/s3_outlet/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'opener-s3-outlet'
|
5
|
+
gem.version = Opener::S3Outlet::VERSION
|
6
|
+
gem.authors = ['development@olery.com']
|
7
|
+
gem.summary = 'S3 Bucket data storing for the web services output when using callbacks.'
|
8
|
+
gem.description = gem.summary
|
9
|
+
gem.homepage = "http://opener-project.github.com/"
|
10
|
+
gem.has_rdoc = 'yard'
|
11
|
+
gem.required_ruby_version = '>= 1.9.2'
|
12
|
+
|
13
|
+
gem.files = Dir.glob([
|
14
|
+
'config/**/*',
|
15
|
+
'exec/**/*',
|
16
|
+
'lib/**/*',
|
17
|
+
'config.ru',
|
18
|
+
'*.gemspec',
|
19
|
+
'README.md',
|
20
|
+
'visualizer.rb'
|
21
|
+
]).select { |file| File.file?(file) }
|
22
|
+
|
23
|
+
gem.executables = Dir.glob('bin/*').map { |file| File.basename(file) }
|
24
|
+
|
25
|
+
gem.add_dependency 'builder'
|
26
|
+
gem.add_dependency 'sinatra', '~>1.4.2'
|
27
|
+
gem.add_dependency 'nokogiri'
|
28
|
+
gem.add_dependency 'httpclient'
|
29
|
+
gem.add_dependency 'uuidtools'
|
30
|
+
gem.add_dependency 'opener-webservice'
|
31
|
+
gem.add_dependency 'opener-daemons'
|
32
|
+
gem.add_dependency 'aws-sdk'
|
33
|
+
|
34
|
+
gem.add_development_dependency 'rspec'
|
35
|
+
gem.add_development_dependency 'cucumber'
|
36
|
+
gem.add_development_dependency 'pry'
|
37
|
+
gem.add_development_dependency 'rake'
|
38
|
+
end
|
data/visualizer.rb
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Opener
|
4
|
+
module Kaf
|
5
|
+
module Visualizer
|
6
|
+
class Parser
|
7
|
+
attr_reader :doc
|
8
|
+
attr_reader :words, :terms, :entities, :properties, :opinions, :document
|
9
|
+
|
10
|
+
def initialize(input_file_handler)
|
11
|
+
@doc = Nokogiri::XML(input_file_handler)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse
|
15
|
+
@words = parse_words
|
16
|
+
@terms = parse_terms
|
17
|
+
@entities = parse_entities
|
18
|
+
@properties = parse_properties
|
19
|
+
@opinions = parse_opinions
|
20
|
+
@document = KAFDocument.new(
|
21
|
+
:words => words,
|
22
|
+
:terms => terms,
|
23
|
+
:entities => entities,
|
24
|
+
:properties => properties,
|
25
|
+
:opinions => opinions
|
26
|
+
)
|
27
|
+
|
28
|
+
return document
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_words
|
32
|
+
parse_elements("//wf", Word)
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_terms
|
36
|
+
# Of course terms should be words here.
|
37
|
+
# Dirty Hack, sufficient for now.
|
38
|
+
parse_elements("//term", Term, :terms=>words)
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_entities
|
42
|
+
parse_elements("//entity", Entity, :terms=>terms)
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_properties
|
46
|
+
parse_elements("//property", Property, :terms=>terms)
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_opinions
|
50
|
+
parse_elements("//opinion", Opinion, :terms=>terms)
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_elements(xpath, klass, opts={})
|
54
|
+
elements = doc.xpath(xpath)
|
55
|
+
lookup_table = Hash.new
|
56
|
+
elements.each do |element|
|
57
|
+
instance = klass.new(element, opts)
|
58
|
+
lookup_table[instance.id] = instance
|
59
|
+
end
|
60
|
+
lookup_table
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class KAFNode
|
66
|
+
attr_reader :content, :targets, :tag, :references
|
67
|
+
|
68
|
+
def initialize(tag, references)
|
69
|
+
@references = references
|
70
|
+
@tag = tag
|
71
|
+
|
72
|
+
set_instance_variables
|
73
|
+
set_content
|
74
|
+
set_targets
|
75
|
+
process_subnodes
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_content
|
79
|
+
@content = tag.content
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_instance_variables
|
83
|
+
tag.keys.each do |key|
|
84
|
+
if respond_to?("#{key}=".to_sym)
|
85
|
+
send("#{key}=".to_sym, tag[key])
|
86
|
+
else
|
87
|
+
instance_variable_set("@#{key}", tag[key])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_targets
|
93
|
+
@targets = []
|
94
|
+
tag.css("span target").each do |target|
|
95
|
+
id = target["id"]
|
96
|
+
@targets << references[:terms][id]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def process_subnodes
|
101
|
+
end
|
102
|
+
|
103
|
+
def has_target?(*ids)
|
104
|
+
ids.flatten.each do |id|
|
105
|
+
return true if target_ids.include?(id)
|
106
|
+
end
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
if targets.size > 0
|
112
|
+
return targets.map(&:to_s).join(" ")
|
113
|
+
else
|
114
|
+
return content
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def target_ids
|
119
|
+
@targets.map(&:id)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class Word < KAFNode
|
125
|
+
attr_reader :wid, :sent, :para, :offset
|
126
|
+
|
127
|
+
def id
|
128
|
+
wid
|
129
|
+
end
|
130
|
+
|
131
|
+
def offset=(offset)
|
132
|
+
@offset = offset.to_i
|
133
|
+
end
|
134
|
+
|
135
|
+
def length
|
136
|
+
content.nil? ? 0 : content.length
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
class Term < KAFNode
|
142
|
+
attr_reader :tid, :type, :lemma, :pos, :morphofeat
|
143
|
+
|
144
|
+
def id
|
145
|
+
tid
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Entity < KAFNode
|
150
|
+
attr_reader :eid, :type
|
151
|
+
|
152
|
+
def id
|
153
|
+
eid
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_s
|
157
|
+
"#{type}: #{targets.map(&:to_s).join(", ")}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class Property < KAFNode
|
162
|
+
attr_reader :pid, :lemma
|
163
|
+
|
164
|
+
def id
|
165
|
+
pid
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_s
|
169
|
+
"#{lemma}: #{targets.map(&:to_s).join(", ")}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class Opinion < KAFNode
|
174
|
+
attr_reader :oid, :expression
|
175
|
+
|
176
|
+
def id
|
177
|
+
oid
|
178
|
+
end
|
179
|
+
|
180
|
+
def process_subnodes
|
181
|
+
@expression = tag.xpath("//opinion_expression").first["polarity"].to_sym
|
182
|
+
end
|
183
|
+
|
184
|
+
def to_s
|
185
|
+
"#{expression}: #{targets.map(&:to_s).join(", ")}"
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
class KAFDocument
|
191
|
+
attr_reader :words, :terms, :entities, :properties, :opinions
|
192
|
+
|
193
|
+
def initialize(opts={})
|
194
|
+
@words = opts.fetch(:words)
|
195
|
+
@terms = opts.fetch(:terms)
|
196
|
+
@entities = opts.fetch(:entities)
|
197
|
+
@properties = opts.fetch(:properties)
|
198
|
+
@opinions = opts.fetch(:opinions)
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
class HTMLTextPresenter
|
204
|
+
attr_reader :document
|
205
|
+
def initialize(document)
|
206
|
+
@document = document
|
207
|
+
end
|
208
|
+
|
209
|
+
def to_html
|
210
|
+
offset = 0
|
211
|
+
prev = Struct.new(:offset).new(0)
|
212
|
+
|
213
|
+
builder = Nokogiri::HTML::Builder.new do |html|
|
214
|
+
html.div(:class=>"opener") do
|
215
|
+
html.p do
|
216
|
+
document.words.values.sort_by(&:offset).each do |word|
|
217
|
+
if offset < word.offset
|
218
|
+
spacer = word.offset - offset
|
219
|
+
spacer = Array.new(spacer, " ").join
|
220
|
+
html.span(spacer)
|
221
|
+
end
|
222
|
+
|
223
|
+
terms = targets_for(:terms, word.id)
|
224
|
+
entities = targets_for(:entities, terms)
|
225
|
+
opinions = opinions_for(terms)
|
226
|
+
properties = targets_for(:properties, terms)
|
227
|
+
|
228
|
+
generics = []
|
229
|
+
generics << "term" if terms.size > 0
|
230
|
+
generics << "entity" if entities.size > 0
|
231
|
+
generics << "opinion" if opinions.size > 0
|
232
|
+
generics << "property" if properties.size > 0
|
233
|
+
|
234
|
+
classes = [terms, entities, opinions, properties, generics].flatten.uniq
|
235
|
+
|
236
|
+
word_annotations = classes.join(" ")
|
237
|
+
|
238
|
+
html.span(word.content, :class=>word_annotations, :id=>word.id)
|
239
|
+
offset = word.offset + word.length
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
[:entities, :opinions, :properties].each do |sym|
|
244
|
+
html.div(:class=>sym) do
|
245
|
+
document.public_send(sym).values.each do |entity|
|
246
|
+
html.div(entity.to_s, :id=>entity.id, :class=>entity.target_ids.join(" "))
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
builder.to_html
|
254
|
+
end
|
255
|
+
|
256
|
+
def targets_for(variable, *ids)
|
257
|
+
targets = document.public_send(variable.to_sym).values.select do |value|
|
258
|
+
value.has_target?(ids.flatten)
|
259
|
+
end
|
260
|
+
|
261
|
+
targets.map(&:id)
|
262
|
+
end
|
263
|
+
|
264
|
+
def opinions_for(*ids)
|
265
|
+
targets = document.opinions.values.select do |value|
|
266
|
+
value.has_target?(ids.flatten)
|
267
|
+
end
|
268
|
+
|
269
|
+
ids = targets.map(&:id)
|
270
|
+
sentiments = targets.map(&:expression)
|
271
|
+
return ids.concat(sentiments).uniq
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
metadata
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: opener-s3-outlet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- development@olery.com
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: builder
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sinatra
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.4.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.4.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: httpclient
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: uuidtools
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: opener-webservice
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: opener-daemons
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: aws-sdk
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: cucumber
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: pry
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rake
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
description: S3 Bucket data storing for the web services output when using callbacks.
|
182
|
+
email:
|
183
|
+
executables:
|
184
|
+
- s3-outlet-daemon
|
185
|
+
- s3-outlet-server
|
186
|
+
extensions: []
|
187
|
+
extra_rdoc_files: []
|
188
|
+
files:
|
189
|
+
- README.md
|
190
|
+
- bin/s3-outlet-daemon
|
191
|
+
- bin/s3-outlet-server
|
192
|
+
- config.ru
|
193
|
+
- config/aws.rb
|
194
|
+
- exec/s3-outlet.rb
|
195
|
+
- lib/opener/s3_outlet.rb
|
196
|
+
- lib/opener/s3_outlet/public/outlet.css
|
197
|
+
- lib/opener/s3_outlet/s3_output.rb
|
198
|
+
- lib/opener/s3_outlet/server.rb
|
199
|
+
- lib/opener/s3_outlet/version.rb
|
200
|
+
- lib/opener/s3_outlet/views/index.erb
|
201
|
+
- lib/opener/s3_outlet/views/show.erb
|
202
|
+
- lib/opener/s3_outlet/views/show.html.erb
|
203
|
+
- lib/opener/s3_outlet/visualizer.rb
|
204
|
+
- opener-s3-outlet.gemspec
|
205
|
+
- visualizer.rb
|
206
|
+
homepage: http://opener-project.github.com/
|
207
|
+
licenses: []
|
208
|
+
metadata: {}
|
209
|
+
post_install_message:
|
210
|
+
rdoc_options: []
|
211
|
+
require_paths:
|
212
|
+
- lib
|
213
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
214
|
+
requirements:
|
215
|
+
- - ">="
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
version: 1.9.2
|
218
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
requirements: []
|
224
|
+
rubyforge_project:
|
225
|
+
rubygems_version: 2.2.2
|
226
|
+
signing_key:
|
227
|
+
specification_version: 4
|
228
|
+
summary: S3 Bucket data storing for the web services output when using callbacks.
|
229
|
+
test_files: []
|
230
|
+
has_rdoc: yard
|