tui 0.0.1
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.
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/README.md +0 -0
- data/Rakefile +3 -0
- data/bin/tui +32 -0
- data/lib/tui.rb +5 -0
- data/lib/tui/authorization.rb +2 -0
- data/lib/tui/authorization/request.rb +41 -0
- data/lib/tui/authorization/response.rb +15 -0
- data/lib/tui/config.rb +30 -0
- data/lib/tui/patch.rb +155 -0
- data/lib/tui/upload.rb +76 -0
- data/lib/tui/version.rb +4 -0
- data/tui.gemspec +28 -0
- metadata +124 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
File without changes
|
data/Rakefile
ADDED
data/bin/tui
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "commander/import"
|
4
|
+
require "tui"
|
5
|
+
|
6
|
+
program :version, Tui::VERSION
|
7
|
+
program :description, Tui::DESCRIPTION
|
8
|
+
|
9
|
+
command :upload do |c|
|
10
|
+
c.syntax = "upload [file]"
|
11
|
+
c.description = "Uploads a file"
|
12
|
+
c.when_called do |args, options|
|
13
|
+
upload = Tui::Upload.new(args[0])
|
14
|
+
|
15
|
+
say "Requesting authorization"
|
16
|
+
request = Tui::Authorization::Request.new(upload)
|
17
|
+
authorization = request.perform
|
18
|
+
|
19
|
+
say "File URL will be #{authorization.short_url}"
|
20
|
+
|
21
|
+
say "Uploading #{upload.path} (#{upload.size} bytes)"
|
22
|
+
|
23
|
+
bar = Commander::UI::ProgressBar.new(upload.chunks, { :complete_message => "Uploaded" })
|
24
|
+
|
25
|
+
upload.perform(authorization) do |bytes_written, bytes_total|
|
26
|
+
bar.increment
|
27
|
+
true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_command :u, :upload
|
data/lib/tui.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "json/add/rails"
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
module Tui
|
5
|
+
module Authorization
|
6
|
+
class Request
|
7
|
+
|
8
|
+
def initialize(upload)
|
9
|
+
@upload = upload
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :upload
|
13
|
+
|
14
|
+
def perform
|
15
|
+
json_request = upload.as_json.to_json
|
16
|
+
|
17
|
+
connection = Excon.new("http://#{Tui::Config.host}/files")
|
18
|
+
|
19
|
+
login = Base64.urlsafe_encode64("#{Tui::Config.username}:#{Tui::Config.password}")
|
20
|
+
|
21
|
+
headers = {
|
22
|
+
"Authorization" => "Basic #{login}",
|
23
|
+
"Accept" => "application/json",
|
24
|
+
"Content-Type" => "application/json",
|
25
|
+
}
|
26
|
+
|
27
|
+
response = connection.request({
|
28
|
+
:method => "POST",
|
29
|
+
:headers => headers,
|
30
|
+
:expects => 200,
|
31
|
+
:body => json_request,
|
32
|
+
})
|
33
|
+
|
34
|
+
json_response = JSON.parse(response.body)
|
35
|
+
|
36
|
+
Response.new(json_response)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Tui
|
2
|
+
module Authorization
|
3
|
+
class Response
|
4
|
+
|
5
|
+
attr_reader :short_url, :upload_url, :headers
|
6
|
+
|
7
|
+
def initialize(json)
|
8
|
+
@short_url = json.delete("short_url")
|
9
|
+
@upload_url = json.delete("upload_url")
|
10
|
+
@headers = json.delete("headers")
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/tui/config.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
module Tui
|
5
|
+
class Config
|
6
|
+
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
class << self
|
12
|
+
[:host, :username, :password].each do |attribute|
|
13
|
+
define_method attribute do
|
14
|
+
instance.config[attribute.to_s]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
path = File.expand_path("~/.tui.yml")
|
23
|
+
|
24
|
+
@config = File.open(path, "r") do |f|
|
25
|
+
YAML.load(f.read)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/tui/patch.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
module Excon
|
2
|
+
class Connection
|
3
|
+
|
4
|
+
CHUNK_SIZE = 128 * 1024
|
5
|
+
|
6
|
+
def request_with_uploadprogress(params, &block)
|
7
|
+
begin
|
8
|
+
# connection has defaults, merge in new params to override
|
9
|
+
params = @connection.merge(params)
|
10
|
+
params[:headers] = @connection[:headers].merge(params[:headers] || {})
|
11
|
+
params[:headers]['Host'] ||= '' << params[:host] << ':' << params[:port]
|
12
|
+
|
13
|
+
# if path is empty or doesn't start with '/', insert one
|
14
|
+
unless params[:path][0, 1] == '/'
|
15
|
+
params[:path].insert(0, '/')
|
16
|
+
end
|
17
|
+
|
18
|
+
if params[:mock]
|
19
|
+
for stub, response in Excon.stubs
|
20
|
+
# all specified non-headers params match and no headers were specified or all specified headers match
|
21
|
+
if [stub.keys - [:headers]].all? {|key| stub[key] == params[key] } &&
|
22
|
+
(!stub.has_key?(:headers) || stub[:headers].keys.all? {|key| stub[:headers][key] == params[:headers][key]})
|
23
|
+
case response
|
24
|
+
when Proc
|
25
|
+
return Excon::Response.new(response.call(params))
|
26
|
+
else
|
27
|
+
return Excon::Response.new(response)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# if we reach here no stubs matched
|
32
|
+
raise(Excon::Errors::StubNotFound.new('no stubs matched ' << params.inspect))
|
33
|
+
end
|
34
|
+
|
35
|
+
# start with "METHOD /path"
|
36
|
+
request = params[:method].to_s.upcase << ' '
|
37
|
+
if @proxy
|
38
|
+
request << params[:scheme] << '://' << params[:host] << ':' << params[:port]
|
39
|
+
end
|
40
|
+
request << params[:path]
|
41
|
+
|
42
|
+
# add query to path, if there is one
|
43
|
+
case params[:query]
|
44
|
+
when String
|
45
|
+
request << '?' << params[:query]
|
46
|
+
when Hash
|
47
|
+
request << '?'
|
48
|
+
for key, values in params[:query]
|
49
|
+
if values.nil?
|
50
|
+
request << key.to_s << '&'
|
51
|
+
else
|
52
|
+
for value in [*values]
|
53
|
+
request << key.to_s << '=' << CGI.escape(value.to_s) << '&'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
request.chop! # remove trailing '&'
|
58
|
+
end
|
59
|
+
|
60
|
+
# finish first line with "HTTP/1.1\r\n"
|
61
|
+
request << HTTP_1_1
|
62
|
+
|
63
|
+
# calculate content length and set to handle non-ascii
|
64
|
+
unless params[:headers].has_key?('Content-Length')
|
65
|
+
params[:headers]['Content-Length'] = case params[:body]
|
66
|
+
when File
|
67
|
+
params[:body].binmode
|
68
|
+
File.size(params[:body])
|
69
|
+
when String
|
70
|
+
if FORCE_ENC
|
71
|
+
params[:body].force_encoding('BINARY')
|
72
|
+
end
|
73
|
+
params[:body].length
|
74
|
+
else
|
75
|
+
0
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# add headers to request
|
80
|
+
for key, values in params[:headers]
|
81
|
+
for value in [*values]
|
82
|
+
request << key.to_s << ': ' << value.to_s << CR_NL
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# add additional "\r\n" to indicate end of headers
|
87
|
+
request << CR_NL
|
88
|
+
|
89
|
+
# write out the request, sans body
|
90
|
+
socket.write(request)
|
91
|
+
socket.flush
|
92
|
+
|
93
|
+
# PATCH BEGINS
|
94
|
+
|
95
|
+
bytes_written = 0
|
96
|
+
bytes_total = params[:headers]['Content-Length']
|
97
|
+
|
98
|
+
# write out the body
|
99
|
+
if params[:body]
|
100
|
+
if params[:body].is_a?(String)
|
101
|
+
socket.write(params[:body])
|
102
|
+
else
|
103
|
+
while chunk = params[:body].read(CHUNK_SIZE)
|
104
|
+
socket.write(chunk)
|
105
|
+
bytes_written += CHUNK_SIZE
|
106
|
+
if params[:upload_progress]
|
107
|
+
params[:upload_progress].call([bytes_written, bytes_total].min, bytes_total)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# PATCH ENDS
|
114
|
+
|
115
|
+
# read the response
|
116
|
+
response = Excon::Response.parse(socket, params, &block)
|
117
|
+
|
118
|
+
if response.headers['Connection'] == 'close'
|
119
|
+
reset
|
120
|
+
end
|
121
|
+
|
122
|
+
response
|
123
|
+
rescue Excon::Errors::StubNotFound => stub_not_found
|
124
|
+
raise(stub_not_found)
|
125
|
+
rescue => socket_error
|
126
|
+
reset
|
127
|
+
raise(Excon::Errors::SocketError.new(socket_error))
|
128
|
+
end
|
129
|
+
|
130
|
+
if params.has_key?(:expects) && ![*params[:expects]].include?(response.status)
|
131
|
+
reset
|
132
|
+
raise(Excon::Errors.status_error(params, response))
|
133
|
+
else
|
134
|
+
response
|
135
|
+
end
|
136
|
+
|
137
|
+
rescue => request_error
|
138
|
+
if params[:idempotent] && [Excon::Errors::SocketError, Excon::Errors::HTTPStatusError].include?(request_error)
|
139
|
+
retries_remaining ||= 4
|
140
|
+
retries_remaining -= 1
|
141
|
+
if retries_remaining > 0
|
142
|
+
if params[:body].respond_to?(:pos=)
|
143
|
+
params[:body].pos = 0
|
144
|
+
end
|
145
|
+
retry
|
146
|
+
else
|
147
|
+
raise(request_error)
|
148
|
+
end
|
149
|
+
else
|
150
|
+
raise(request_error)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
data/lib/tui/upload.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
require "mime/types"
|
3
|
+
require "excon"
|
4
|
+
|
5
|
+
module Tui
|
6
|
+
|
7
|
+
class Upload
|
8
|
+
|
9
|
+
attr_reader :path
|
10
|
+
attr_accessor :authorization
|
11
|
+
|
12
|
+
def initialize(path)
|
13
|
+
@path = File.realpath(path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def file
|
17
|
+
@file ||= File.new(path, "r")
|
18
|
+
end
|
19
|
+
|
20
|
+
def size
|
21
|
+
@size ||= File.size(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def original_filename
|
25
|
+
@original_filename ||= File.basename(path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def md5sum
|
29
|
+
@digest ||= Digest::MD5.file(path)
|
30
|
+
@md5sum ||= @digest.hexdigest
|
31
|
+
end
|
32
|
+
|
33
|
+
def mime_type
|
34
|
+
@mime_type ||= MIME::Types.type_for(path).first.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def chunks
|
38
|
+
@chunks ||= (size / Excon::Connection::CHUNK_SIZE.to_f).ceil
|
39
|
+
end
|
40
|
+
|
41
|
+
def perform(authorization, &block)
|
42
|
+
connection = Excon.new(authorization.upload_url)
|
43
|
+
|
44
|
+
headers = {
|
45
|
+
"User-Agent" => user_agent,
|
46
|
+
}.merge(authorization.headers)
|
47
|
+
|
48
|
+
connection.request_with_uploadprogress(
|
49
|
+
:method => "PUT",
|
50
|
+
:body => file,
|
51
|
+
:headers => headers,
|
52
|
+
:expects => 200,
|
53
|
+
:upload_progress => block
|
54
|
+
)
|
55
|
+
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def as_json
|
60
|
+
{
|
61
|
+
:original_filename => original_filename,
|
62
|
+
:md5sum => md5sum,
|
63
|
+
:size => size,
|
64
|
+
:mime_type => mime_type
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def user_agent
|
71
|
+
"tui-#{Tui::VERSION}"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
data/lib/tui/version.rb
ADDED
data/tui.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "tui/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "tui"
|
7
|
+
s.version = Tui::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Steve Hoeksema"]
|
10
|
+
s.email = ["steve@seven.net.nz"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = Tui::DESCRIPTION
|
13
|
+
s.description = Tui::DESCRIPTION
|
14
|
+
|
15
|
+
s.rubyforge_project = "tui"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency "commander", "~> 4.0"
|
23
|
+
s.add_dependency "excon", "~> 0.6"
|
24
|
+
s.add_dependency "mime-types", "~> 1.1"
|
25
|
+
s.add_dependency "json"
|
26
|
+
|
27
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tui
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Steve Hoeksema
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-18 00:00:00 +12:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: commander
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "4.0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: excon
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0.6"
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: mime-types
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "1.1"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: json
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: rspec
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "2.6"
|
69
|
+
type: :development
|
70
|
+
version_requirements: *id005
|
71
|
+
description: A collection of command line utilities to interact with tu.is
|
72
|
+
email:
|
73
|
+
- steve@seven.net.nz
|
74
|
+
executables:
|
75
|
+
- tui
|
76
|
+
extensions: []
|
77
|
+
|
78
|
+
extra_rdoc_files: []
|
79
|
+
|
80
|
+
files:
|
81
|
+
- .gitignore
|
82
|
+
- Gemfile
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- bin/tui
|
86
|
+
- lib/tui.rb
|
87
|
+
- lib/tui/authorization.rb
|
88
|
+
- lib/tui/authorization/request.rb
|
89
|
+
- lib/tui/authorization/response.rb
|
90
|
+
- lib/tui/config.rb
|
91
|
+
- lib/tui/patch.rb
|
92
|
+
- lib/tui/upload.rb
|
93
|
+
- lib/tui/version.rb
|
94
|
+
- tui.gemspec
|
95
|
+
has_rdoc: true
|
96
|
+
homepage: ""
|
97
|
+
licenses: []
|
98
|
+
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: "0"
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: "0"
|
116
|
+
requirements: []
|
117
|
+
|
118
|
+
rubyforge_project: tui
|
119
|
+
rubygems_version: 1.5.0
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: A collection of command line utilities to interact with tu.is
|
123
|
+
test_files: []
|
124
|
+
|