linguist_ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Carl Lerche, Yehuda Katz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
File without changes
data/REST.md ADDED
File without changes
data/ROADMAP.md ADDED
File without changes
data/bin/linguist ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'linguist'
7
+ require 'linguist/command'
8
+
9
+ args = ARGV.dup
10
+ ARGV.clear
11
+ command = args.shift.strip rescue 'help'
12
+
13
+ Linguist::Command.run(command, args)
14
+
@@ -0,0 +1,120 @@
1
+ require 'rest_client'
2
+ require 'uri'
3
+ require 'time'
4
+ require 'linguist/version'
5
+ require 'vendor/okjson'
6
+ require 'json'
7
+ require 'linguist/models/projects'
8
+
9
+ # A Ruby class to call the Linguist REST API. You might use this if you want to
10
+ # manage your Linguist apps from within a Ruby program, such as Capistrano.
11
+ #
12
+ # Example:
13
+ #
14
+ # require 'linguist'
15
+ # linguist = Linguist::Client.new('me@example.com', 'mypass')
16
+ # linguist.create('myapp')
17
+ #
18
+ class Linguist::Client
19
+
20
+ def self.version
21
+ Linguist::VERSION
22
+ end
23
+
24
+ def self.gem_version_string
25
+ "linguist-gem/#{version}"
26
+ end
27
+
28
+ attr_accessor :host, :user, :auth_token
29
+
30
+ def self.auth(user, password, host='lingui.st')
31
+ client = new(user, password, host)
32
+ OkJson.decode client.post('/sessions', { :email => user, :password => password }, :accept => 'json').to_s
33
+ end
34
+
35
+ def initialize(user, auth_token, host='lingui.st')
36
+ @user = user
37
+ @auth_token = auth_token
38
+ @host = host
39
+ end
40
+
41
+ def project(title)
42
+ project = self.projects[title]
43
+ raise(CommandFailed, "=== You aren't associated for a project named '#{title}'") if project.nil?
44
+ project
45
+ end
46
+
47
+ def projects
48
+ return Linguist::Models::Projects.new(self)
49
+ end
50
+
51
+ def get(uri, extra_headers={ }) # :nodoc:
52
+ process(:get, uri, extra_headers)
53
+ end
54
+
55
+ def post(uri, payload="", extra_headers={ }) # :nodoc:
56
+ process(:post, uri, extra_headers, payload)
57
+ end
58
+
59
+ def put(uri, payload, extra_headers={ }) # :nodoc:
60
+ process(:put, uri, extra_headers, payload)
61
+ end
62
+
63
+ def delete(uri, extra_headers={ }) # :nodoc:
64
+ process(:delete, uri, extra_headers)
65
+ end
66
+
67
+ def process(method, uri, extra_headers={ }, payload=nil)
68
+ headers = linguist_headers.merge(extra_headers)
69
+ # payload = auth_params.merge(payload)
70
+ args = [method, payload, headers].compact
71
+ response = resource(uri).send(*args)
72
+
73
+ puts "RESPONSE #{response}"
74
+
75
+ # extract_warning(response)
76
+ response
77
+ end
78
+
79
+ def resource(uri)
80
+
81
+ RestClient.proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
82
+ if uri =~ /^https?/
83
+ # RestClient::Resource.new(uri, user, auth_token)
84
+ RestClient::Resource.new(uri)
85
+ elsif host =~ /^https?/
86
+ # RestClient::Resource.new(host, user, auth_token)[uri]
87
+ RestClient::Resource.new(host)[uri]
88
+ else
89
+ # RestClient::Resource.new("https://api.#{host}", user, password)[uri]
90
+ # RestClient::Resource.new("http://localhost:3000/api/v1", user, auth_token)[uri]
91
+ RestClient::Resource.new("http://localhost:3000/api/v1")[uri]
92
+ end
93
+ end
94
+
95
+ def extract_warning(response)
96
+ # return unless response
97
+ # if response.headers[:x_heroku_warning] && @warning_callback
98
+ # warning = response.headers[:x_heroku_warning]
99
+ # @displayed_warnings ||= { }
100
+ # unless @displayed_warnings[warning]
101
+ # @warning_callback.call(warning)
102
+ # @displayed_warnings[warning] = true
103
+ # end
104
+ # end
105
+ end
106
+
107
+ def linguist_headers # :nodoc:
108
+ {
109
+ 'X-Linguist-API-Version' => '1',
110
+ 'X-Linguist-User-Email' => user,
111
+ 'X-Linguist-User-Auth-Token' => auth_token,
112
+ 'User-Agent' => self.class.gem_version_string,
113
+ 'X-Ruby-Version' => RUBY_VERSION,
114
+ 'X-Ruby-Platform' => RUBY_PLATFORM,
115
+ 'content_type' => 'json',
116
+ 'accept' => 'json'
117
+ }
118
+ end
119
+
120
+ end
@@ -0,0 +1,98 @@
1
+ require 'linguist/helpers'
2
+ require 'linguist/commands/base'
3
+
4
+ Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
5
+
6
+ module Linguist
7
+ module Command
8
+ class InvalidCommand < RuntimeError; end
9
+ class CommandFailed < RuntimeError; end
10
+
11
+ extend Linguist::Helpers
12
+
13
+ class << self
14
+
15
+ def run(command, args, retries=0)
16
+ begin
17
+ run_internal 'auth:reauthorize', args.dup if retries > 0
18
+ run_internal(command, args.dup)
19
+ rescue InvalidCommand
20
+ error "Unknown command. Run 'linguist help' for usage information."
21
+ # rescue RestClient::Unauthorized
22
+ # if retries < 3
23
+ # STDERR.puts "Authentication failure"
24
+ # run(command, args, retries+1)
25
+ # else
26
+ # error "Authentication failure"
27
+ # end
28
+ # rescue RestClient::ResourceNotFound => e
29
+ # error extract_not_found(e.http_body)
30
+ # rescue RestClient::RequestFailed => e
31
+ # error extract_error(e.http_body) unless e.http_code == 402
32
+ # retry if run_internal('account:confirm_billing', args.dup)
33
+ # rescue RestClient::RequestTimeout
34
+ # error "API request timed out. Please try again, or contact support@lingui.st if this issue persists."
35
+ rescue CommandFailed => e
36
+ error e.message
37
+ rescue Interrupt => e
38
+ error "\n[canceled]"
39
+ end
40
+ end
41
+
42
+ def run_internal(command, args, linguist=nil)
43
+ klass, method = parse(command)
44
+ runner = klass.new(args, linguist)
45
+ raise InvalidCommand unless runner.respond_to?(method)
46
+ runner.send(method)
47
+ end
48
+
49
+ def parse(command)
50
+ parts = command.split(':')
51
+ case parts.size
52
+ when 1
53
+ begin
54
+ return eval("Linguist::Command::#{command.capitalize}"), :index
55
+ rescue NameError, NoMethodError
56
+ return Linguist::Command::App, command.to_sym
57
+ end
58
+ else
59
+ begin
60
+ const = Linguist::Command
61
+ command = parts.pop
62
+ parts.each { |part| const = const.const_get(part.capitalize) }
63
+ return const, command.to_sym
64
+ rescue NameError
65
+ raise InvalidCommand
66
+ end
67
+ end
68
+ end
69
+
70
+ # def extract_not_found(body)
71
+ # body =~ /^[\w\s]+ not found$/ ? body : "Resource not found"
72
+ # end
73
+ #
74
+ # def extract_error(body)
75
+ # msg = parse_error_xml(body) || parse_error_json(body) || parse_error_plain(body) || 'Internal server error'
76
+ # msg.split("\n").map { |line| ' ! ' + line }.join("\n")
77
+ # end
78
+ #
79
+ # def parse_error_xml(body)
80
+ # xml_errors = REXML::Document.new(body).elements.to_a("//errors/error")
81
+ # msg = xml_errors.map { |a| a.text }.join(" / ")
82
+ # return msg unless msg.empty?
83
+ # rescue Exception
84
+ # end
85
+ #
86
+ # def parse_error_json(body)
87
+ # json = OkJson.decode(body.to_s)
88
+ # json['error']
89
+ # rescue OkJson::ParserError
90
+ # end
91
+ #
92
+ # def parse_error_plain(body)
93
+ # return unless body.respond_to?(:headers) && body.headers[:content_type].include?("text/plain")
94
+ # body.to_s
95
+ # end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,72 @@
1
+ require 'readline'
2
+ require 'launchy'
3
+
4
+ module Linguist::Command
5
+ class App < Base
6
+ def login
7
+ Linguist::Command.run_internal "auth:reauthorize", args.dup
8
+ end
9
+
10
+ def logout
11
+ Linguist::Command.run_internal "auth:delete_credentials", args.dup
12
+ display "Local credentials cleared."
13
+ end
14
+
15
+ def list
16
+ list = linguist.projects.all
17
+ if list.size > 0
18
+ display "Projects:\n" + list.keys.map { |name|
19
+ "- #{name}"
20
+ }.join("\n")
21
+ else
22
+ display "You have no projects."
23
+ end
24
+ end
25
+
26
+ def create
27
+ title = args.shift.downcase.strip rescue nil
28
+ title ||= extract_from_dir_name
29
+ linguist.projects.create title
30
+ display("Created #{title}")
31
+ end
32
+
33
+ def rename
34
+ newtitle = args.shift.downcase.strip rescue ''
35
+ oldtitle = project.title
36
+
37
+ raise(CommandFailed, "Invalid name.") if newtitle == ''
38
+
39
+ project.update(:title => newtitle)
40
+ display("Renaming project from #{oldtitle} to #{newtitle}")
41
+
42
+ display app_urls(newtitle)
43
+ end
44
+
45
+ def info
46
+ display "=== #{project.title}"
47
+ display "Web URL: #{project.weburl}"
48
+ display "Owner: #{project.owner}"
49
+ display "Number of translation: #{project.translations_count}"
50
+ end
51
+
52
+ def open
53
+ project = linguist.project
54
+ url = project.weburl
55
+ Launchy.open url
56
+ end
57
+
58
+ def destroy
59
+ display "=== #{project.title}"
60
+ display "Web URL: #{project.weburl}"
61
+ display "Owner: #{project.owner}"
62
+
63
+ if confirm_command(project_title)
64
+ redisplay "Destroying #{project_title} ... "
65
+ project.destroy
66
+ display "done"
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,146 @@
1
+ require "linguist/client"
2
+
3
+ module Linguist::Command
4
+ class Auth < Base
5
+ attr_accessor :credentials
6
+
7
+ def client
8
+ @client ||= init_linguist
9
+ end
10
+
11
+ def init_linguist
12
+ client = Linguist::Client.new(user, auth_token, host)
13
+ # client.on_warning { |msg| self.display("\n#{msg}\n\n") }
14
+ client
15
+ end
16
+
17
+ def host
18
+ ENV['LINGUIST_HOST'] || 'lingui.st'
19
+ end
20
+
21
+ # just a stub; will raise if not authenticated
22
+ def check
23
+ client.list
24
+ end
25
+
26
+ def reauthorize
27
+ @credentials = ask_for_and_save_credentials
28
+ end
29
+
30
+ def user # :nodoc:
31
+ get_credentials
32
+ @credentials[0]
33
+ end
34
+
35
+ def auth_token # :nodoc:
36
+ get_credentials
37
+ @credentials[1]
38
+ end
39
+
40
+ def credentials_file
41
+ "#{home_directory}/.linguist/credentials"
42
+ end
43
+
44
+ def get_credentials # :nodoc:
45
+ return if @credentials
46
+ unless @credentials = read_credentials
47
+ ask_for_and_save_credentials
48
+ end
49
+ @credentials
50
+ end
51
+
52
+ def read_credentials
53
+ File.exists?(credentials_file) and File.read(credentials_file).split("\n")
54
+ end
55
+
56
+ def echo_off
57
+ system "stty -echo"
58
+ end
59
+
60
+ def echo_on
61
+ system "stty echo"
62
+ end
63
+
64
+ def ask_for_credentials
65
+ # puts "Enter your Linguist credentials."
66
+
67
+ print "Email: "
68
+ user = "hjuskewycz@hemju.com"#ask
69
+
70
+ print "Password: "
71
+ password = "testtest"#running_on_windows? ? ask_for_password_on_windows : ask_for_password
72
+ api_key = Linguist::Client.auth(user, password, host)['api_key']
73
+
74
+ [user, api_key]
75
+ end
76
+
77
+ def ask_for_password_on_windows
78
+ require "Win32API"
79
+ char = nil
80
+ password = ''
81
+
82
+ while char = Win32API.new("crtdll", "_getch", [], "L").Call do
83
+ break if char == 10 || char == 13 # received carriage return or newline
84
+ if char == 127 || char == 8 # backspace and delete
85
+ password.slice!(-1, 1)
86
+ else
87
+ # windows might throw a -1 at us so make sure to handle RangeError
88
+ (password << char.chr) rescue RangeError
89
+ end
90
+ end
91
+ puts
92
+ return password
93
+ end
94
+
95
+ def ask_for_password
96
+ echo_off
97
+ password = ask
98
+ puts
99
+ echo_on
100
+ return password
101
+ end
102
+
103
+ def ask_for_and_save_credentials
104
+ begin
105
+ @credentials = ask_for_credentials
106
+ write_credentials
107
+ check
108
+ rescue ::RestClient::Unauthorized, ::RestClient::ResourceNotFound => e
109
+ puts "EXCEPTION #{e}"
110
+ delete_credentials
111
+ @client = nil
112
+ @credentials = nil
113
+ display "Authentication failed."
114
+ retry if retry_login?
115
+ exit 1
116
+ rescue Exception => e
117
+ delete_credentials
118
+ raise e
119
+ end
120
+ end
121
+
122
+ def retry_login?
123
+ @login_attempts ||= 0
124
+ @login_attempts += 1
125
+ @login_attempts < 3
126
+ end
127
+
128
+ def write_credentials
129
+ FileUtils.mkdir_p(File.dirname(credentials_file))
130
+ f = File.open(credentials_file, 'w')
131
+ f.chmod(0600)
132
+ f.puts self.credentials
133
+ f.close
134
+ set_credentials_permissions
135
+ end
136
+
137
+ def set_credentials_permissions
138
+ FileUtils.chmod 0700, File.dirname(credentials_file)
139
+ FileUtils.chmod 0600, credentials_file
140
+ end
141
+
142
+ def delete_credentials
143
+ # FileUtils.rm_f(credentials_file)
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,118 @@
1
+ require 'fileutils'
2
+
3
+ module Linguist::Command
4
+ class Base
5
+ include Linguist::Helpers
6
+
7
+ attr_accessor :args
8
+ attr_reader :autodetected_app
9
+
10
+ def initialize(args, linguist=nil)
11
+ @args = args
12
+ @linguist = linguist
13
+ @autodetected_app = false
14
+ end
15
+
16
+ def linguist
17
+ @linguist ||= Linguist::Command.run_internal('auth:client', args)
18
+ end
19
+
20
+ def extract_app(force=true)
21
+ app = extract_option('--app', false)
22
+ raise(CommandFailed, "You must specify an app name after --app") if app == false
23
+ unless app
24
+ app = extract_app_from_git || extract_from_dir_name ||
25
+ raise(CommandFailed, "No app specified.\nRun this command from app folder or set it adding --app <app name>") if force
26
+ @autodetected_app = true
27
+ end
28
+ app
29
+ end
30
+
31
+ def extract_from_dir_name
32
+ dir = Dir.pwd
33
+ File.basename(dir)
34
+ end
35
+
36
+ def extract_app_from_git
37
+ dir = Dir.pwd
38
+ return unless remotes = git_remotes(dir)
39
+
40
+ if remote = extract_option('--remote')
41
+ remotes[remote]
42
+ elsif remote = extract_app_from_git_config
43
+ remotes[remote]
44
+ else
45
+ apps = remotes.values.uniq
46
+ return apps.first if apps.size == 1
47
+ end
48
+ end
49
+
50
+ def extract_app_from_git_config
51
+ remote = git("config heroku.remote")
52
+ remote == "" ? nil : remote
53
+ end
54
+
55
+ def git_remotes(base_dir=Dir.pwd)
56
+ remotes = { }
57
+ original_dir = Dir.pwd
58
+ Dir.chdir(base_dir)
59
+
60
+ # TODO
61
+ # git("remote -v").split("\n").each do |remote|
62
+ # name, url, method = remote.split(/\s/)
63
+ # if url =~ /^git@#{heroku.host}:([\w\d-]+)\.git$/
64
+ # remotes[name] = $1
65
+ # end
66
+ # end
67
+
68
+ Dir.chdir(original_dir)
69
+ remotes
70
+ end
71
+
72
+ def extract_option(options, default=true)
73
+ values = options.is_a?(Array) ? options : [options]
74
+ return unless opt_index = args.select { |a| values.include? a }.first
75
+ opt_position = args.index(opt_index) + 1
76
+ if args.size > opt_position && opt_value = args[opt_position]
77
+ if opt_value.include?('--')
78
+ opt_value = nil
79
+ else
80
+ args.delete_at(opt_position)
81
+ end
82
+ end
83
+ opt_value ||= default
84
+ args.delete(opt_index)
85
+ block_given? ? yield(opt_value) : opt_value
86
+ end
87
+
88
+ def git_url(name)
89
+ "git@#{heroku.host}:#{name}.git"
90
+ end
91
+
92
+ def app_urls(name)
93
+ # "#{web_url(name)} | #{git_url(name)}"
94
+ end
95
+
96
+ def escape(value)
97
+ linguist.escape(value)
98
+ end
99
+
100
+ def project(title=nil)
101
+ title ||= project_title
102
+ @project ||= linguist.project(title)
103
+ end
104
+
105
+ def project_title
106
+ (args.first && !args.first =~ /^\-\-/) ? args.first : extract_app
107
+ end
108
+ end
109
+
110
+ class BaseWithApp < Base
111
+ attr_accessor :app
112
+
113
+ def initialize(args, linguist=nil)
114
+ super(args, linguist)
115
+ @app ||= extract_app
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,90 @@
1
+ module Linguist::Command
2
+ class Help < Base
3
+ class HelpGroup < Array
4
+
5
+ attr_reader :title
6
+
7
+ def initialize(title)
8
+ @title = title
9
+ end
10
+
11
+ def command(name, description)
12
+ self << [name, description]
13
+ end
14
+
15
+ def space
16
+ self << ['', '']
17
+ end
18
+ end
19
+
20
+ def self.groups
21
+ @groups ||= []
22
+ end
23
+
24
+ def self.group(title, &block)
25
+ groups << begin
26
+ group = HelpGroup.new(title)
27
+ yield group
28
+ group
29
+ end
30
+ end
31
+
32
+ def self.create_default_groups!
33
+ return if @defaults_created
34
+ @defaults_created = true
35
+ group 'General Commands' do |group|
36
+ group.command 'help', 'show this usage'
37
+ group.command 'version', 'show the gem version'
38
+ group.space
39
+ # group.command 'login', 'log in with your linguist credentials'
40
+ # group.command 'logout', 'clear local authentication credentials'
41
+ # group.space
42
+ group.command 'list', 'list your projects'
43
+ # group.command 'create [<name>]', 'create a new app'
44
+ group.command 'info', 'show app info, like web url and number of translations'
45
+ group.command 'open', 'open the app in a web browser'
46
+ group.command 'rename <newname>', 'rename the app'
47
+ group.command 'destroy', 'destroy the app permanently'
48
+ group.space
49
+ end
50
+
51
+ # group 'Plugins' do |group|
52
+ # group.command 'plugins', 'list installed plugins'
53
+ # group.command 'plugins:install <url>', 'install the plugin from the specified git url'
54
+ # group.command 'plugins:uninstall <url/name>', 'remove the specified plugin'
55
+ # end
56
+ end
57
+
58
+ def index
59
+ display usage
60
+ end
61
+
62
+ def version
63
+ display Linguist::Client.version
64
+ end
65
+
66
+ def usage
67
+ longest_command_length = self.class.groups.map do |group|
68
+ group.map { |g| g.first.length }
69
+ end.flatten.max
70
+
71
+ self.class.groups.inject(StringIO.new) do |output, group|
72
+ output.puts "=== %s" % group.title
73
+ output.puts
74
+
75
+ group.each do |command, description|
76
+ if command.empty?
77
+ output.puts
78
+ else
79
+ output.puts "%-*s # %s" % [longest_command_length, command, description]
80
+ end
81
+ end
82
+
83
+ output.puts
84
+ output
85
+ end.string
86
+ end
87
+ end
88
+ end
89
+
90
+ Linguist::Command::Help.create_default_groups!