transcriptic 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.
- data/bin/transcriptic +12 -0
- data/lib/transcriptic.rb +3 -0
- data/lib/transcriptic/api.rb +73 -0
- data/lib/transcriptic/api/errors.rb +24 -0
- data/lib/transcriptic/api/sequences.rb +33 -0
- data/lib/transcriptic/api/version.rb +5 -0
- data/lib/transcriptic/auth.rb +223 -0
- data/lib/transcriptic/cli.rb +12 -0
- data/lib/transcriptic/client.rb +332 -0
- data/lib/transcriptic/command.rb +233 -0
- data/lib/transcriptic/command/analyze.rb +20 -0
- data/lib/transcriptic/command/base.rb +195 -0
- data/lib/transcriptic/command/console.rb +10 -0
- data/lib/transcriptic/command/data.rb +8 -0
- data/lib/transcriptic/command/help.rb +124 -0
- data/lib/transcriptic/command/history.rb +8 -0
- data/lib/transcriptic/command/login.rb +35 -0
- data/lib/transcriptic/command/run.rb +60 -0
- data/lib/transcriptic/command/sequences.rb +45 -0
- data/lib/transcriptic/command/status.rb +17 -0
- data/lib/transcriptic/helpers.rb +311 -0
- data/lib/transcriptic/version.rb +3 -0
- data/lib/vendor/transcriptic/okjson.rb +557 -0
- metadata +161 -0
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'transcriptic/helpers'
|
2
|
+
require "optparse"
|
3
|
+
|
4
|
+
#Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
5
|
+
|
6
|
+
module Transcriptic
|
7
|
+
module Command
|
8
|
+
class InvalidCommand < RuntimeError; end
|
9
|
+
class CommandFailed < RuntimeError; end
|
10
|
+
|
11
|
+
extend Transcriptic::Helpers
|
12
|
+
|
13
|
+
def self.load
|
14
|
+
Dir[File.join(File.dirname(__FILE__), "command", "*.rb")].each do |file|
|
15
|
+
require file
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.commands
|
20
|
+
@@commands ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.command_aliases
|
24
|
+
@@command_aliases ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.files
|
28
|
+
@@files ||= Hash.new {|hash,key| hash[key] = File.readlines(key).map {|line| line.strip}}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.namespaces
|
32
|
+
@@namespaces ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.register_command(command)
|
36
|
+
commands[command[:command]] = command
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.register_namespace(namespace)
|
40
|
+
namespaces[namespace[:name]] = namespace
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.current_command
|
44
|
+
@current_command
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.current_command=(new_current_command)
|
48
|
+
@current_command = new_current_command
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.current_args
|
52
|
+
@current_args
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.current_options
|
56
|
+
@current_options
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.global_options
|
60
|
+
@global_options ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.invalid_arguments
|
64
|
+
@invalid_arguments
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.shift_argument
|
68
|
+
@invalid_arguments.shift.downcase rescue nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.validate_arguments!
|
72
|
+
unless invalid_arguments.empty?
|
73
|
+
arguments = invalid_arguments.map {|arg| "\"#{arg}\""}
|
74
|
+
if arguments.length == 1
|
75
|
+
message = "Invalid argument: #{arguments.first}"
|
76
|
+
elsif arguments.length > 1
|
77
|
+
message = "Invalid arguments: "
|
78
|
+
message << arguments[0...-1].join(", ")
|
79
|
+
message << " and "
|
80
|
+
message << arguments[-1]
|
81
|
+
end
|
82
|
+
$stderr.puts(format_with_bang(message))
|
83
|
+
run(current_command, ["--help"])
|
84
|
+
exit(1)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.global_option(name, *args)
|
89
|
+
global_options << { :name => name, :args => args }
|
90
|
+
end
|
91
|
+
|
92
|
+
global_option :confirm, "--confirm PROTOCOL"
|
93
|
+
global_option :help, "--help", "-h"
|
94
|
+
global_option :remote, "--remote REMOTE"
|
95
|
+
|
96
|
+
def self.prepare_run(cmd, args=[])
|
97
|
+
command = parse(cmd)
|
98
|
+
|
99
|
+
unless command
|
100
|
+
if %w( -v --version ).include?(cmd)
|
101
|
+
display Transcriptic::VERSION
|
102
|
+
exit
|
103
|
+
end
|
104
|
+
|
105
|
+
output_with_bang("`#{cmd}` is not a transcriptic command.")
|
106
|
+
|
107
|
+
distances = {}
|
108
|
+
(commands.keys + command_aliases.keys).each do |suggestion|
|
109
|
+
distance = string_distance(cmd, suggestion)
|
110
|
+
distances[distance] ||= []
|
111
|
+
distances[distance] << suggestion
|
112
|
+
end
|
113
|
+
|
114
|
+
if distances.keys.min < 4
|
115
|
+
suggestions = distances[distances.keys.min].sort
|
116
|
+
if suggestions.length == 1
|
117
|
+
output_with_bang("Perhaps you meant `#{suggestions.first}`.")
|
118
|
+
else
|
119
|
+
output_with_bang("Perhaps you meant #{suggestions[0...-1].map {|suggestion| "`#{suggestion}`"}.join(', ')} or `#{suggestions.last}`.")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
output_with_bang("See `transcriptic help` for additional details.")
|
124
|
+
exit(1)
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
@current_command = cmd
|
129
|
+
|
130
|
+
opts = {}
|
131
|
+
invalid_options = []
|
132
|
+
|
133
|
+
parser = OptionParser.new do |parser|
|
134
|
+
# overwrite OptionParsers Officious['version'] to avoid conflicts
|
135
|
+
# see: https://github.com/ruby/ruby/blob/trunk/lib/optparse.rb#L814
|
136
|
+
parser.on("--version") do |value|
|
137
|
+
invalid_options << "--version"
|
138
|
+
end
|
139
|
+
global_options.each do |global_option|
|
140
|
+
parser.on(*global_option[:args]) do |value|
|
141
|
+
global_option[:proc].call(value) if global_option[:proc]
|
142
|
+
opts[global_option[:name]] = value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
command[:options].each do |name, option|
|
146
|
+
parser.on("-#{option[:short]}", "--#{option[:long]}", option[:desc]) do |value|
|
147
|
+
opts[name.gsub("-", "_").to_sym] = value
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
begin
|
153
|
+
parser.order!(args) do |nonopt|
|
154
|
+
invalid_options << nonopt
|
155
|
+
end
|
156
|
+
rescue OptionParser::InvalidOption => ex
|
157
|
+
invalid_options << ex.args.first
|
158
|
+
retry
|
159
|
+
end
|
160
|
+
|
161
|
+
if opts[:help]
|
162
|
+
args.unshift cmd unless cmd =~ /^-.*/
|
163
|
+
cmd = "help"
|
164
|
+
command = parse(cmd)
|
165
|
+
end
|
166
|
+
|
167
|
+
args.concat(invalid_options)
|
168
|
+
|
169
|
+
@current_args = args
|
170
|
+
@current_options = opts
|
171
|
+
@invalid_arguments = invalid_options
|
172
|
+
|
173
|
+
[ command[:klass].new(args.dup, opts.dup), command[:method] ]
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.run(cmd, arguments=[])
|
177
|
+
object, method = prepare_run(cmd, arguments.dup)
|
178
|
+
object.send(method)
|
179
|
+
rescue RestClient::Unauthorized
|
180
|
+
puts "Authentication failure"
|
181
|
+
unless ENV['TRANSCRIPTIC_API_KEY']
|
182
|
+
run "login"
|
183
|
+
retry
|
184
|
+
end
|
185
|
+
rescue RestClient::PaymentRequired => e
|
186
|
+
retry if run('account:confirm_billing', arguments.dup)
|
187
|
+
rescue RestClient::ResourceNotFound => e
|
188
|
+
error extract_error(e.http_body) {
|
189
|
+
e.http_body =~ /^([\w\s]+ not found).?$/ ? $1 : "Resource not found"
|
190
|
+
}
|
191
|
+
rescue RestClient::Locked => e
|
192
|
+
app = e.response.headers[:x_confirmation_required]
|
193
|
+
if confirm_command(app, extract_error(e.response.body))
|
194
|
+
arguments << '--confirm' << app
|
195
|
+
retry
|
196
|
+
end
|
197
|
+
rescue RestClient::RequestTimeout
|
198
|
+
error "API request timed out. Please try again, or contact support@transcriptic.com if this issue persists."
|
199
|
+
rescue RestClient::RequestFailed => e
|
200
|
+
error extract_error(e.http_body)
|
201
|
+
rescue CommandFailed => e
|
202
|
+
error e.message
|
203
|
+
rescue OptionParser::ParseError => ex
|
204
|
+
commands[cmd] ? run("help", [cmd]) : run("help")
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.parse(cmd)
|
208
|
+
commands[cmd] || commands[command_aliases[cmd]]
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.extract_error(body, options={})
|
212
|
+
default_error = block_given? ? yield : "Internal server error.\nRun 'try status' to check for known platform issues."
|
213
|
+
parse_error_xml(body) || parse_error_json(body) || parse_error_plain(body) || default_error
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.parse_error_xml(body)
|
217
|
+
xml_errors = REXML::Document.new(body).elements.to_a("//errors/error")
|
218
|
+
msg = xml_errors.map { |a| a.text }.join(" / ")
|
219
|
+
return msg unless msg.empty?
|
220
|
+
rescue Exception
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.parse_error_json(body)
|
224
|
+
json = json_decode(body.to_s) rescue false
|
225
|
+
json ? json['error'] : nil
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.parse_error_plain(body)
|
229
|
+
return unless body.respond_to?(:headers) && body.headers[:content_type].to_s.include?("text/plain")
|
230
|
+
body.to_s
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "transcriptic/command/base"
|
2
|
+
|
3
|
+
module Transcriptic::Command
|
4
|
+
|
5
|
+
# analyze an autoprotocol script or project
|
6
|
+
#
|
7
|
+
# analyze a
|
8
|
+
#
|
9
|
+
#Example:
|
10
|
+
#
|
11
|
+
# $ heroku run bash
|
12
|
+
# Running `bash` attached to terminal... up, run.1
|
13
|
+
# ~ $
|
14
|
+
#
|
15
|
+
class Analyze < Base
|
16
|
+
|
17
|
+
def index; end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "transcriptic/auth"
|
3
|
+
require "transcriptic/command"
|
4
|
+
|
5
|
+
class Transcriptic::Command::Base
|
6
|
+
include Transcriptic::Helpers
|
7
|
+
|
8
|
+
def self.namespace
|
9
|
+
self.to_s.split("::").last.downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :args
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
def initialize(args=[], options={})
|
16
|
+
@args = args
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def transcriptic
|
21
|
+
Transcriptic::Auth.client
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def self.inherited(klass)
|
27
|
+
unless klass == Transcriptic::Command::Base
|
28
|
+
help = extract_help_from_caller(caller.first)
|
29
|
+
|
30
|
+
Transcriptic::Command.register_namespace(
|
31
|
+
:name => klass.namespace,
|
32
|
+
:description => help.first
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.method_added(method)
|
38
|
+
return if self == Transcriptic::Command::Base
|
39
|
+
return if private_method_defined?(method)
|
40
|
+
return if protected_method_defined?(method)
|
41
|
+
|
42
|
+
help = extract_help_from_caller(caller.first)
|
43
|
+
resolved_method = (method.to_s == "index") ? nil : method.to_s
|
44
|
+
command = [ self.namespace, resolved_method ].compact.join(":")
|
45
|
+
banner = extract_banner(help) || command
|
46
|
+
|
47
|
+
Transcriptic::Command.register_command(
|
48
|
+
:klass => self,
|
49
|
+
:method => method,
|
50
|
+
:namespace => self.namespace,
|
51
|
+
:command => command,
|
52
|
+
:banner => banner.strip,
|
53
|
+
:help => help.join("\n"),
|
54
|
+
:summary => extract_summary(help),
|
55
|
+
:description => extract_description(help),
|
56
|
+
:options => extract_options(help)
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.alias_command(new, old)
|
61
|
+
raise "no such command: #{old}" unless Transcriptic::Command.commands[old]
|
62
|
+
Transcriptic::Command.command_aliases[new] = old
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.extract_help_from_caller(line)
|
66
|
+
# pull out of the caller the information for the file path and line number
|
67
|
+
if line =~ /^(.+?):(\d+)/
|
68
|
+
extract_help($1, $2)
|
69
|
+
else
|
70
|
+
raise("unable to extract help from caller: #{line}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.extract_help(file, line_number)
|
75
|
+
buffer = []
|
76
|
+
lines = Transcriptic::Command.files[file]
|
77
|
+
|
78
|
+
(line_number.to_i-2).downto(0) do |i|
|
79
|
+
line = lines[i]
|
80
|
+
case line[0..0]
|
81
|
+
when ""
|
82
|
+
when "#"
|
83
|
+
buffer.unshift(line[1..-1])
|
84
|
+
else
|
85
|
+
break
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
buffer
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.extract_command(help)
|
93
|
+
extract_banner(help).to_s.split(" ").first
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.extract_banner(help)
|
97
|
+
help.first
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.extract_summary(help)
|
101
|
+
extract_description(help).split("\n").first
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.extract_description(help)
|
105
|
+
help.reject do |line|
|
106
|
+
line =~ /^\s+-(.+)#(.+)/
|
107
|
+
end.join("\n")
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.extract_options(help)
|
111
|
+
help.select do |line|
|
112
|
+
line =~ /^\s+-(.+)#(.+)/
|
113
|
+
end.inject({}) do |hash, line|
|
114
|
+
description = line.split("#", 2).last
|
115
|
+
long = line.match(/--([A-Za-z\- ]+)/)[1].strip
|
116
|
+
short = line.match(/-([A-Za-z ])/)[1].strip
|
117
|
+
hash.update(long.split(" ").first => { :desc => description, :short => short, :long => long })
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def current_command
|
122
|
+
Transcriptic::Command.current_command
|
123
|
+
end
|
124
|
+
|
125
|
+
def extract_option(name, default=true)
|
126
|
+
key = name.gsub("--", "").to_sym
|
127
|
+
return unless options[key]
|
128
|
+
value = options[key] || default
|
129
|
+
block_given? ? yield(value) : value
|
130
|
+
end
|
131
|
+
|
132
|
+
def invalid_arguments
|
133
|
+
Transcriptic::Command.invalid_arguments
|
134
|
+
end
|
135
|
+
|
136
|
+
def shift_argument
|
137
|
+
Transcriptic::Command.shift_argument
|
138
|
+
end
|
139
|
+
|
140
|
+
def validate_arguments!
|
141
|
+
Transcriptic::Command.validate_arguments!
|
142
|
+
end
|
143
|
+
|
144
|
+
def confirm_mismatch?
|
145
|
+
options[:confirm] && (options[:confirm] != options[:app])
|
146
|
+
end
|
147
|
+
|
148
|
+
def extract_app_in_dir(dir)
|
149
|
+
return unless remotes = git_remotes(dir)
|
150
|
+
|
151
|
+
if remote = options[:remote]
|
152
|
+
remotes[remote]
|
153
|
+
elsif remote = extract_app_from_git_config
|
154
|
+
remotes[remote]
|
155
|
+
else
|
156
|
+
apps = remotes.values.uniq
|
157
|
+
if apps.size == 1
|
158
|
+
apps.first
|
159
|
+
else
|
160
|
+
raise(Transcriptic::Command::CommandFailed, "Multiple apps in folder and no app specified.\nSpecify which app to use with --app <app name>")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def extract_app_from_git_config
|
166
|
+
remote = git("config transcriptic.remote")
|
167
|
+
remote == "" ? nil : remote
|
168
|
+
end
|
169
|
+
|
170
|
+
def git_remotes(base_dir=Dir.pwd)
|
171
|
+
remotes = {}
|
172
|
+
original_dir = Dir.pwd
|
173
|
+
Dir.chdir(base_dir)
|
174
|
+
|
175
|
+
git("remote -v").split("\n").each do |remote|
|
176
|
+
name, url, method = remote.split(/\s/)
|
177
|
+
if url =~ /^git@#{transcriptic.host}:([\w\d-]+)\.git$/
|
178
|
+
remotes[name] = $1
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
Dir.chdir(original_dir)
|
183
|
+
remotes
|
184
|
+
end
|
185
|
+
|
186
|
+
def escape(value)
|
187
|
+
transcriptic.escape(value)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
module Transcriptic::Command
|
192
|
+
unless const_defined?(:BaseWithApp)
|
193
|
+
BaseWithApp = Base
|
194
|
+
end
|
195
|
+
end
|