linguist_ruby 0.0.2

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/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!