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.
@@ -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