transcriptic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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