t 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ *.gem
2
+ *.rbc
3
+ .DS_Store
4
+ .bundle
5
+ .rvmrc
6
+ .yardoc
7
+ Gemfile.lock
8
+ coverage/*
9
+ doc/*
10
+ log/*
11
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby
6
+ - rbx
7
+ - rbx-2.0
8
+ - ree
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'http://rubygems.org'
2
+
3
+ platforms :jruby do
4
+ gem 'jruby-openssl', '~> 0.7'
5
+ end
6
+
7
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Erik Michaels-Ober
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
@@ -0,0 +1,192 @@
1
+ # Twitter CLI
2
+ A command-line interface for Twitter, powered by the [twitter gem][gem]. The
3
+ CLI attempts to mimic the [Twitter SMS commands][sms] wherever possible,
4
+ however it offers more commands than are available via SMS.
5
+
6
+ [gem]: https://rubygems.org/gems/twitter
7
+ [sms]: https://support.twitter.com/articles/14020-twitter-sms-command
8
+
9
+ ## <a name="history">History</a>
10
+ ![History](http://twitter.rubyforge.org/images/terminal_output.png "History")
11
+
12
+ The [twitter gem][gem] previously contained a command-line interface, up until
13
+ version 0.5.0, when it was [removed][]. This project is offered as a sucessor
14
+ to that effort, however it is a clean room implementation that contains none of
15
+ John Nunemaker's original code.
16
+
17
+ [removed]: https://github.com/jnunemaker/twitter/commit/dd2445e3e2c97f38b28a3f32ea902536b3897adf
18
+
19
+ ## <a name="installation">Installation</a>
20
+ gem install t
21
+
22
+ ## <a name="ci">Continuous Integration</a>
23
+ [![Build Status](https://secure.travis-ci.org/sferik/t.png)][travis]
24
+
25
+ [travis]: http://travis-ci.org/sferik/t
26
+
27
+ ## <a name="examples">Usage Examples</a>
28
+ Typing `t help` will give you a list of all the available commands. You can
29
+ type `t help TASK` to get help for a specific command.
30
+
31
+ t help
32
+
33
+ Because Twitter requires OAuth for most of it's functionality, you'll need to
34
+ register a new application at <http://dev.twitter.com/apps/new>. Once you
35
+ create your application make sure to set the "Application Type" to "Read, Write
36
+ and Access direct messages", otherwise you won't be able to post status updates
37
+ or send direct messages via the CLI.
38
+
39
+ Once you have registered your application, you'll be assigned a consumer key
40
+ and secret, which you can use to authorize your Twitter account.
41
+
42
+ t authorize --consumer-key YOUR_CONSUMER_KEY --consumer-secret YOUR_CONSUMER_SECRET
43
+
44
+ This will open a new browser window where you can authenticate to Twitter.
45
+
46
+ You can see a list of all the accounts you've authorized.
47
+
48
+ t accounts
49
+
50
+ You can easily switch between accounts.
51
+
52
+ t set default sferik
53
+
54
+ Incidentally, account information is stored in YAML format in `~/.trc`.
55
+
56
+ ### <a name="update">Update your status</a>
57
+
58
+ t update "I'm tweeting from the command line, powered by @gem."
59
+
60
+ ### <a name="dm">Send a user a private message</a>
61
+
62
+ t dm sferik "Want to get dinner together tonight?"
63
+
64
+ ### <a name="location">Update the location field in your profile</a>
65
+
66
+ t set location San Francisco
67
+
68
+ ### <a name="get">Retrieve the latest Tweet posted by a user</a>
69
+
70
+ t get sferik
71
+
72
+ ### <a name="whois">Retrieve profile information for a user</a>
73
+
74
+ t whois sferik
75
+
76
+ ### <a name="stats">Get stats about a user</a>
77
+
78
+ t stats sferik
79
+
80
+ ### <a name="suggest">Return a listing of users you might enjoy following</a>
81
+
82
+ t suggest
83
+
84
+ ### <a name="follow">Start following a user</a>
85
+
86
+ t follow sferik
87
+
88
+ ### <a name="leave">Stop following a user</a>
89
+
90
+ t unfollow sferik
91
+
92
+ ### <a name="timeline">Retrieve the timeline of status updates from users you are following</a>
93
+
94
+ t timeline
95
+
96
+ ### <a name="mentions">Retrieve the timeline of status updates that mention you</a>
97
+
98
+ t mentions
99
+
100
+ ### <a name="reply">Reply to a Tweet</a>
101
+
102
+ t reply sferik Thanks
103
+
104
+ ### <a name="retweet">Send another user's latest Tweet to your followers</a>
105
+
106
+ t retweet sferik
107
+
108
+ ### <a name="favorite">Mark a user's latest Tweet as one of your favorites</a>
109
+
110
+ t favorite sferik
111
+
112
+ ## <a name="contributing">Contributing</a>
113
+ In the spirit of [free software][fsf], **everyone** is encouraged to help
114
+ improve this project.
115
+
116
+ [fsf]: http://www.fsf.org/licensing/essays/free-sw.html
117
+
118
+ Here are some ways *you* can contribute:
119
+
120
+ * by using alpha, beta, and prerelease versions
121
+ * by reporting bugs
122
+ * by suggesting new features
123
+ * by writing or editing documentation
124
+ * by writing specifications
125
+ * by writing code (**no patch is too small**: fix typos, add comments, clean up
126
+ inconsistent whitespace)
127
+ * by refactoring code
128
+ * by closing [issues][]
129
+ * by reviewing patches
130
+ * by financially (please send bitcoin donations to
131
+ 1KxSo9bGBfPVFEtWNLpnUK1bfLNNT4q31L)
132
+
133
+ [issues]: https://github.com/sferik/t/issues
134
+
135
+ ## <a name="issues">Submitting an Issue</a>
136
+ We use the [GitHub issue tracker][issues] to track bugs and features. Before
137
+ submitting a bug report or feature request, check to make sure it hasn't
138
+ already been submitted. You can indicate support for an existing issuse by
139
+ voting it up. When submitting a bug report, please include a
140
+ [Gist](https://gist.github.com/) that includes a stack trace and any details
141
+ that may be necessary to reproduce the bug, including your gem version, Ruby
142
+ version, and operating system. Ideally, a bug report should include a pull
143
+ request with failing specs.
144
+
145
+ ## <a name="pulls">Submitting a Pull Request</a>
146
+ 1. Fork the project.
147
+ 2. Create a topic branch.
148
+ 3. Implement your feature or bug fix.
149
+ 4. Add documentation for your feature or bug fix.
150
+ 5. Run `bundle exec rake doc:yard`. If your changes are not 100%
151
+ documented, go back to step 4.
152
+ 6. Add specs for your feature or bug fix.
153
+ 7. Run `bundle exec rake spec`. If your changes are not 100% covered, go
154
+ back to step 6.
155
+ 8. Commit and push your changes.
156
+ 9. Submit a pull request. Please do not include changes to the gemspec,
157
+ version, or history file. (If you want to create your own version for some
158
+ reason, please do so in a separate commit.)
159
+
160
+ ## <a name="rubies">Supported Rubies</a>
161
+ This library aims to support and is [tested against][travis] the following Ruby
162
+ implementations:
163
+
164
+ * Ruby 1.8.7
165
+ * Ruby 1.9.1
166
+ * Ruby 1.9.2
167
+ * [JRuby][]
168
+ * [Rubinius][]
169
+ * [Ruby Enterprise Edition][ree]
170
+
171
+ [jruby]: http://www.jruby.org/
172
+ [rubinius]: http://rubini.us/
173
+ [ree]: http://www.rubyenterpriseedition.com/
174
+
175
+ If something doesn't work on one of these interpreters, it should be considered
176
+ a bug.
177
+
178
+ This library may inadvertently work (or seem to work) on other Ruby
179
+ implementations, however support will only be provided for the versions listed
180
+ above.
181
+
182
+ If you would like this library to support another Ruby version, you may
183
+ volunteer to be a maintainer. Being a maintainer entails making sure all tests
184
+ run and pass on that implementation. When something breaks on your
185
+ implementation, you will be personally responsible for providing patches in a
186
+ timely fashion. If critical issues for a particular implementation exist at the
187
+ time of a major release, support for that Ruby version may be dropped.
188
+
189
+ ## <a name="copyright">Copyright</a>
190
+ Copyright (c) 2011 Erik Michaels-Ober. See [LICENSE][] for details.
191
+
192
+ [license]: https://github.com/sferik/t/blob/master/LICENSE.md
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task :test => :spec
10
+ task :default => :spec
data/bin/t ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 't'
4
+
5
+ begin
6
+ T::CLI.start(ARGV)
7
+ rescue Interrupt
8
+ puts "Quitting..."
9
+ end
data/lib/t.rb ADDED
@@ -0,0 +1 @@
1
+ require 't/cli'
data/lib/t/cli.rb ADDED
@@ -0,0 +1,294 @@
1
+ require 'action_view'
2
+ require 'launchy'
3
+ require 'oauth'
4
+ require 't/rcfile'
5
+ require 't/set'
6
+ require 'thor'
7
+ require 'time'
8
+ require 'twitter'
9
+ require 'yaml'
10
+
11
+ module T
12
+ class CLI < Thor
13
+ DEFAULT_HOST = 'api.twitter.com'
14
+ DEFAULT_PROTOCOL = 'https'
15
+
16
+ class_option "host", :aliases => "-H", :default => DEFAULT_HOST
17
+ class_option "no-ssl", :aliases => "-U", :type => :boolean, :default => false
18
+
19
+ include ActionView::Helpers::DateHelper
20
+ include ActionView::Helpers::NumberHelper
21
+
22
+ desc "accounts", "List accounts"
23
+ def accounts
24
+ rcfile = RCFile.instance
25
+ rcfile.profiles.each do |profile|
26
+ say profile[0]
27
+ profile[1].keys.each do |key|
28
+ say " #{key}#{rcfile.default_profile[0] == profile[0] && rcfile.default_profile[1] == key ? " (default)" : nil}"
29
+ end
30
+ end
31
+ end
32
+ map %w(list ls) => :accounts
33
+
34
+ desc "authorize", "Allows an application to request user authorization"
35
+ option "consumer-key", :aliases => "-c", :required => true
36
+ option "consumer-secret", :aliases => "-s", :required => true
37
+ option "access-token", :aliases => "-a"
38
+ option "token-secret", :aliases => "-S"
39
+ def authorize
40
+ request_token = consumer.get_request_token
41
+ url = generate_authorize_url(request_token)
42
+ say "Authorize this app and copy the supplied PIN to complete the authorization process."
43
+ print "Your default web browser will open in "
44
+ 9.downto(1) do |i|
45
+ sleep 0.2
46
+ print i
47
+ 4.times do
48
+ sleep 0.2
49
+ print '.'
50
+ end
51
+ end
52
+ Launchy.open(url)
53
+ pin = ask "\nPaste in the supplied PIN:"
54
+ access_token = request_token.get_access_token(:oauth_verifier => pin.chomp)
55
+ oauth_response = access_token.get('/1/account/verify_credentials.json')
56
+ username = oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first
57
+ rcfile = RCFile.instance
58
+ rcfile[username] = {
59
+ options['consumer-key'] => {
60
+ 'username' => username,
61
+ 'consumer_key' => options['consumer-key'],
62
+ 'consumer_secret' => options['consumer-secret'],
63
+ 'token' => access_token.token,
64
+ 'secret' => access_token.secret,
65
+ }
66
+ }
67
+ rcfile.default_profile = {'username' => username, 'consumer_key' => options['consumer-key']}
68
+ say "Authorization successful"
69
+ rescue OAuth::Unauthorized
70
+ raise Exception, "Authorization failed. Check that your consumer key and secret are correct, as well as your username and password."
71
+ end
72
+
73
+ desc "block USERNAME", "Block a user."
74
+ def block(username)
75
+ client.block(username)
76
+ say "Blocked @#{username}"
77
+ say
78
+ say "Run `#{$0} unblock #{username}` to unblock."
79
+ end
80
+
81
+ desc "direct_messages", "Returns the 20 most recent Direct Messages sent to you."
82
+ def direct_messages
83
+ client.direct_messages.each do |direct_message|
84
+ say "#{direct_message.sender.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
85
+ end
86
+ end
87
+
88
+ desc "sent_messages", "Returns the 20 most recent Direct Messages sent to you."
89
+ def sent_messages
90
+ client.direct_messages_sent.each do |direct_message|
91
+ say "#{direct_message.recipient.screen_name.rjust(20)}: #{direct_message.text} (#{time_ago_in_words(direct_message.created_at)} ago)"
92
+ end
93
+ end
94
+
95
+ desc "dm USERNAME MESSAGE", "Sends that person a Direct Message."
96
+ def dm(username, message)
97
+ direct_message = client.direct_message_create(username, message)
98
+ say "Direct Message sent to @#{username} (#{time_ago_in_words(status.created_at)} ago)"
99
+ end
100
+ map :m => :dm
101
+
102
+ desc "favorite USERNAME", "Marks that user's last Tweet as one of your favorites."
103
+ def favorite(username)
104
+ status = client.user_timeline(username).first
105
+ begin
106
+ client.favorite(status.id)
107
+ say "You have favorited @#{username}'s latest tweet: #{status.text}"
108
+ rescue Twitter::Error::Forbidden => error
109
+ say "You have already favorited this status."
110
+ end
111
+ end
112
+
113
+ desc "follow USERNAME", "Allows you to start following a specific user."
114
+ def follow(username)
115
+ user = client.follow(username)
116
+ say "You're now following @#{username}. Run `#{$0} unfollow #{username}` to stop."
117
+ recommendations = client.recommendations(:user_id => user.id, :limit => 2)
118
+ say
119
+ say "Try following @#{recommendations[0].screen_name} or @#{recommendations[1].screen_name}."
120
+ status = client.user_timeline(username).first
121
+ say "#{username}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
122
+ end
123
+ map :befriend => :follow
124
+
125
+ desc "get USERNAME", "Retrieves the latest update posted by the user."
126
+ def get(username)
127
+ status = client.user_timeline(username).first
128
+ say "#{status.text} (#{time_ago_in_words(status.created_at)} ago)"
129
+ end
130
+
131
+ desc "mentions", "Returns the 20 most recent Tweets mentioning you."
132
+ option "reverse", :aliases => "-r", :type => :boolean, :default => false
133
+ def mentions
134
+ timeline = client.mentions
135
+ timeline.reverse! if options['reverse']
136
+ timeline.each do |status|
137
+ say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
138
+ end
139
+ end
140
+ map :replies => :mentions
141
+
142
+ desc "open USERNAME", "Opens that user's profile in a web browser."
143
+ def open(username)
144
+ Launchy.open("https://twitter.com/#{username}")
145
+ end
146
+
147
+ desc "reply USERNAME MESSAGE", "Post your Tweet as a reply directed at another person."
148
+ def reply(username, message)
149
+ in_reply_to_status = client.user_timeline(username).first
150
+ status = client.update("@#{username} #{message}", :in_reply_to_status_id => in_reply_to_status.id)
151
+ say "Reply created (#{time_ago_in_words(status.created_at)} ago)"
152
+ end
153
+
154
+ desc "retweet USERNAME", "Sends that user's latest Tweet to your followers."
155
+ def retweet(username)
156
+ status = client.user_timeline(username).first
157
+ client.retweet(status.id)
158
+ say "You have retweeted @#{username}'s latest tweet: #{status.text}"
159
+ end
160
+ map :rt => :retweet
161
+
162
+ desc "stats USERNAME", "Retrieves the given user's number of followers and how many people they're following."
163
+ def stats(username)
164
+ user = client.user(username)
165
+ say "Followers: #{number_with_delimiter(user.followers_count)}"
166
+ say "Following: #{number_with_delimiter(user.friends_count)}"
167
+ say
168
+ say "Run `#{$0} whois #{username}` to view profile."
169
+ end
170
+
171
+ desc "suggest", "This command returns a listing of Twitter users' accounts we think you might enjoy following."
172
+ def suggest
173
+ recommendations = client.recommendations(:limit => 2)
174
+ say "Try following @#{recommendations[0].screen_name} or @#{recommendations[1].screen_name}."
175
+ say
176
+ say "Run `#{$0} follow USERNAME` to follow."
177
+ say "Run `#{$0} whois USERNAME` for profile."
178
+ say "Run `#{$0} suggest` for more."
179
+ end
180
+
181
+ desc "timeline", "Returns the 20 most recent Tweets posted by you and the users you follow."
182
+ option "reverse", :aliases => "-r", :type => :boolean, :default => false
183
+ def timeline
184
+ timeline = client.home_timeline
185
+ timeline.reverse! if options['reverse']
186
+ timeline.each do |status|
187
+ say "#{status.user.screen_name.rjust(20)}: #{status.text} (#{time_ago_in_words(status.created_at)} ago)"
188
+ end
189
+ end
190
+ map :tl => :timeline
191
+
192
+ desc "unblock USERNAME", "Unblock a user."
193
+ def unblock(username)
194
+ client.unblock(username)
195
+ say "Unblocked @#{username}"
196
+ say
197
+ say "Run `#{$0} block #{username}` to block."
198
+ end
199
+
200
+ desc "unfavorite USERNAME", "Marks that user's last Tweet as one of your favorites."
201
+ def unfavorite(username)
202
+ status = client.user_timeline(username).first
203
+ client.unfavorite(status.id)
204
+ say "You have unfavorited @#{username}'s latest tweet: #{status.text}"
205
+ end
206
+
207
+ desc "unfollow USERNAME", "Allows you to stop following a specific user."
208
+ def unfollow(username)
209
+ user = client.unfollow(username)
210
+ say "You are no longer following @#{username}. Run `#{$0} follow #{username}` to follow again."
211
+ end
212
+ map :defriend => :unfollow
213
+
214
+ desc "update MESSAGE", "Post a Tweet."
215
+ def update(message)
216
+ status = client.update(message)
217
+ say "Tweet created (#{time_ago_in_words(status.created_at)} ago)"
218
+ end
219
+ map :post => :update
220
+
221
+ desc "whois USERNAME", "Retrieves profile information for the user."
222
+ def whois(username)
223
+ user = client.user(username)
224
+ output = []
225
+ output << "#{user.name}, since #{user.created_at.strftime("%b %Y")}."
226
+ output << "bio: #{user.description}"
227
+ output << "location: #{user.location}"
228
+ output << "web: #{user.url}"
229
+ say output.join("\n")
230
+ end
231
+
232
+ desc "version", "Show version"
233
+ def version
234
+ say T::Version
235
+ end
236
+ map %w(-v --version) => :version
237
+
238
+ desc "set SUBCOMMAND ...ARGS", "Change various account settings."
239
+ subcommand 'set', Set
240
+
241
+ no_tasks do
242
+
243
+ def access_token
244
+ OAuth::AccessToken.new(consumer, token, secret)
245
+ end
246
+
247
+ def base_url
248
+ "#{protocol}://#{host}"
249
+ end
250
+
251
+ def client
252
+ rcfile = RCFile.instance
253
+ Twitter::Client.new(
254
+ :endpoint => base_url,
255
+ :consumer_key => rcfile.default_consumer_key,
256
+ :consumer_secret => rcfile.default_consumer_secret,
257
+ :oauth_token => rcfile.default_token,
258
+ :oauth_token_secret => rcfile.default_secret
259
+ )
260
+ end
261
+
262
+ def consumer
263
+ OAuth::Consumer.new(
264
+ options['consumer-key'],
265
+ options['consumer-secret'],
266
+ :site => base_url
267
+ )
268
+ end
269
+
270
+ def generate_authorize_url(request_token)
271
+ request = consumer.create_signed_request(:get, consumer.authorize_path, request_token, pin_auth_parameters)
272
+ params = request['Authorization'].sub(/^OAuth\s+/, '').split(/,\s+/).map do |param|
273
+ key, value = param.split('=')
274
+ value =~ /"(.*?)"/
275
+ "#{key}=#{CGI::escape($1)}"
276
+ end.join('&')
277
+ "#{base_url}#{request.path}?#{params}"
278
+ end
279
+
280
+ def host
281
+ options['host'] || DEFAULT_HOST
282
+ end
283
+
284
+ def pin_auth_parameters
285
+ {:oauth_callback => 'oob'}
286
+ end
287
+
288
+ def protocol
289
+ options['no-ssl'] ? 'http' : DEFAULT_PROTOCOL
290
+ end
291
+
292
+ end
293
+ end
294
+ end
data/lib/t/rcfile.rb ADDED
@@ -0,0 +1,98 @@
1
+ # rcfile = RCFile.instance (should load current data from)
2
+ # rcfile[profile] = {}
3
+ # rcfile.write
4
+
5
+ require 'singleton'
6
+ require 'yaml'
7
+
8
+ class RCFile
9
+ FILE_NAME = '.trc'
10
+ attr_reader :path
11
+
12
+ include Singleton
13
+
14
+ def initialize
15
+ @path = File.join(File.expand_path("~"), FILE_NAME)
16
+ @data = load
17
+ end
18
+
19
+ def [](username)
20
+ profiles[username]
21
+ end
22
+
23
+ def []=(username, profile)
24
+ profiles[username] ||= {}
25
+ profiles[username].merge!(profile)
26
+ write
27
+ end
28
+
29
+ def configuration
30
+ @data['configuration']
31
+ end
32
+
33
+ def default_consumer_key
34
+ profiles[default_profile[0]][default_profile[1]]['consumer_key'] if default_profile && profiles[default_profile[0]] && profiles[default_profile[0]][default_profile[1]]
35
+ end
36
+
37
+ def default_consumer_secret
38
+ profiles[default_profile[0]][default_profile[1]]['consumer_secret'] if default_profile && profiles[default_profile[0]] && profiles[default_profile[0]][default_profile[1]]
39
+ end
40
+
41
+ def default_profile
42
+ configuration['default_profile']
43
+ end
44
+
45
+ def default_profile=(profile)
46
+ configuration['default_profile'] = [profile['username'], profile['consumer_key']]
47
+ write
48
+ end
49
+
50
+ def default_secret
51
+ profiles[default_profile[0]][default_profile[1]]['secret'] if default_profile && profiles[default_profile[0]] && profiles[default_profile[0]][default_profile[1]]
52
+ end
53
+
54
+ def default_token
55
+ profiles[default_profile[0]][default_profile[1]]['token'] if default_profile && profiles[default_profile[0]] && profiles[default_profile[0]][default_profile[1]]
56
+ end
57
+
58
+ def delete
59
+ File.delete(@path) if File.exist?(@path)
60
+ end
61
+
62
+ def empty?
63
+ @data == default_structure
64
+ end
65
+
66
+ def load
67
+ YAML.load_file(@path)
68
+ rescue Errno::ENOENT
69
+ default_structure
70
+ end
71
+
72
+ def path=(path)
73
+ @path = path
74
+ @data = load
75
+ @path
76
+ end
77
+
78
+ def profiles
79
+ @data['profiles']
80
+ end
81
+
82
+ def reset
83
+ self.send(:initialize)
84
+ end
85
+
86
+ private
87
+
88
+ def default_structure
89
+ {'configuration' => {}, 'profiles' => {}}
90
+ end
91
+
92
+ def write
93
+ File.open(@path, 'w') do |rcfile|
94
+ rcfile.write @data.to_yaml
95
+ end
96
+ end
97
+
98
+ end
data/lib/t/set.rb ADDED
@@ -0,0 +1,74 @@
1
+ require 't/rcfile'
2
+ require 'thor'
3
+ require 'twitter'
4
+
5
+ module T
6
+ class Set < Thor
7
+ DEFAULT_HOST = 'api.twitter.com'
8
+ DEFAULT_PROTOCOL = 'https'
9
+
10
+ desc "bio DESCRIPTION", "Edits your Bio information on your Twitter profile."
11
+ def bio(description)
12
+ client.update_profile(:description => description)
13
+ say "Bio has been changed."
14
+ end
15
+
16
+ desc "default USERNAME, CONSUMER_KEY", "Set your default account."
17
+ def default(username, consumer_key)
18
+ rcfile = RCFile.instance
19
+ rcfile.default_profile = {'username' => username, 'consumer_key' => consumer_key}
20
+ say "Default account has been changed."
21
+ end
22
+
23
+ desc "language LANGUAGE_NAME", "Selects the language you'd like to receive notifications in."
24
+ def language(language_name)
25
+ client.settings(:language => language_name)
26
+ say "Language has been changed."
27
+ end
28
+
29
+ desc "location PLACE_NAME", "Updates the location field in your profile."
30
+ def location(place_name)
31
+ client.update_profile(:location => place_name)
32
+ say "Location has been changed."
33
+ end
34
+
35
+ desc "name NAME", "Sets the name field on your Twitter profile."
36
+ def name(name)
37
+ client.update_profile(:name => name)
38
+ say "Name has been changed."
39
+ end
40
+
41
+ desc "url URL", "Sets the URL field on your profile."
42
+ def url(url)
43
+ client.update_profile(:url => url)
44
+ say "URL has been changed."
45
+ end
46
+
47
+ no_tasks do
48
+
49
+ def base_url
50
+ "#{protocol}://#{host}"
51
+ end
52
+
53
+ def client
54
+ rcfile = RCFile.instance
55
+ Twitter::Client.new(
56
+ :endpoint => base_url,
57
+ :consumer_key => rcfile.default_consumer_key,
58
+ :consumer_secret => rcfile.default_consumer_secret,
59
+ :oauth_token => rcfile.default_token,
60
+ :oauth_token_secret => rcfile.default_secret
61
+ )
62
+ end
63
+
64
+ def host
65
+ parent_options['host'] || DEFAULT_HOST
66
+ end
67
+
68
+ def protocol
69
+ parent_options['no-ssl'] ? 'http' : DEFAULT_PROTOCOL
70
+ end
71
+
72
+ end
73
+ end
74
+ end
data/lib/t/version.rb ADDED
@@ -0,0 +1,30 @@
1
+ module T
2
+ class Version
3
+
4
+ # @return [Integer]
5
+ def self.major
6
+ 0
7
+ end
8
+
9
+ # @return [Integer]
10
+ def self.minor
11
+ 0
12
+ end
13
+
14
+ # @return [Integer]
15
+ def self.patch
16
+ 1
17
+ end
18
+
19
+ # @return [String, NilClass]
20
+ def self.pre
21
+ nil
22
+ end
23
+
24
+ # @return [String]
25
+ def self.to_s
26
+ [major, minor, patch, pre].compact.join('.')
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ ---
2
+ profiles:
3
+ sferik:
4
+ abc123:
5
+ username: sferik
6
+ consumer_key: abc123
7
+ consumer_secret: asdfasd223sd2
8
+ token: 7505382-cebdct6bwobn
9
+ secret: epzrjvxtumoc
10
+ configuration:
11
+ default_profile:
12
+ - sferik
13
+ - abc123
data/spec/helper.rb ADDED
@@ -0,0 +1,7 @@
1
+ major, minor, patch = RUBY_VERSION.split('.')
2
+ $KCODE = 'u' if major.to_i == 1 && minor.to_i < 9
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+ require 't'
6
+ require 'rspec'
7
+ require 'webmock/rspec'
@@ -0,0 +1,197 @@
1
+ require 'helper'
2
+
3
+ describe RCFile do
4
+
5
+ after do
6
+ RCFile.instance.reset
7
+ end
8
+
9
+ it 'should be a singleton class' do
10
+ RCFile.should be_a Class
11
+ lambda do
12
+ RCFile.new
13
+ end.should raise_error(NoMethodError, "private method `new' called for RCFile:Class")
14
+ end
15
+
16
+ describe '#[]' do
17
+ it 'should return the profiles for a user' do
18
+ rcfile = RCFile.instance
19
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
20
+ rcfile['sferik'].keys.should == ['abc123']
21
+ end
22
+ end
23
+
24
+ describe '#[]=' do
25
+ it 'should add a profile for a user' do
26
+ rcfile = RCFile.instance
27
+ rcfile.path = '/tmp/trc'
28
+ rcfile['sferik'] = {
29
+ 'abc123' => {
30
+ :username => 'sferik',
31
+ :consumer_key => 'abc123',
32
+ :consumer_secret => 'def456',
33
+ :token => 'ghi789',
34
+ :secret => 'jkl012',
35
+ }
36
+ }
37
+ rcfile['sferik'].keys.should == ['abc123']
38
+ end
39
+ it 'should write the data to disk' do
40
+ rcfile = RCFile.instance
41
+ rcfile.path = '/tmp/trc'
42
+ rcfile['sferik'] = {
43
+ 'abc123' => {
44
+ :username => 'sferik',
45
+ :consumer_key => 'abc123',
46
+ :consumer_secret => 'def456',
47
+ :token => 'ghi789',
48
+ :secret => 'jkl012',
49
+ }
50
+ }
51
+ rcfile.load
52
+ rcfile['sferik'].keys.should == ['abc123']
53
+ rcfile.delete
54
+ end
55
+ end
56
+
57
+ describe '#configuration' do
58
+ it 'should return configuration' do
59
+ rcfile = RCFile.instance
60
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
61
+ rcfile.configuration.keys.should == ['default_profile']
62
+ end
63
+ end
64
+
65
+ describe '#default_consumer_key' do
66
+ it 'should return default consumer key' do
67
+ rcfile = RCFile.instance
68
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
69
+ rcfile.default_consumer_key.should == 'abc123'
70
+ end
71
+ end
72
+
73
+ describe '#default_consumer_secret' do
74
+ it 'should return default consumer secret' do
75
+ rcfile = RCFile.instance
76
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
77
+ rcfile.default_consumer_secret.should == 'asdfasd223sd2'
78
+ end
79
+ end
80
+
81
+ describe '#default_profile' do
82
+ it 'should return default profile' do
83
+ rcfile = RCFile.instance
84
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
85
+ rcfile.default_profile.should == ['sferik', 'abc123']
86
+ end
87
+ end
88
+
89
+ describe '#default_profile=' do
90
+ it 'should set default profile' do
91
+ rcfile = RCFile.instance
92
+ rcfile.path = File.expand_path('/tmp/trc', __FILE__)
93
+ rcfile.load
94
+ rcfile.delete
95
+ rcfile.default_profile = {'username' => 'sferik', 'consumer_key' => 'abc123'}
96
+ rcfile.default_profile.should == ['sferik', 'abc123']
97
+ end
98
+ it 'should write the data to disk' do
99
+ rcfile = RCFile.instance
100
+ rcfile.path = '/tmp/trc'
101
+ rcfile.default_profile = {'username' => 'sferik', 'consumer_key' => 'abc123'}
102
+ rcfile.load
103
+ rcfile.default_profile.should == ['sferik', 'abc123']
104
+ rcfile.delete
105
+ end
106
+ end
107
+
108
+ describe '#default_token' do
109
+ it 'should return default token' do
110
+ rcfile = RCFile.instance
111
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
112
+ rcfile.default_token.should == '7505382-cebdct6bwobn'
113
+ end
114
+ end
115
+
116
+ describe '#default_secret' do
117
+ it 'should return default secret' do
118
+ rcfile = RCFile.instance
119
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
120
+ rcfile.default_secret.should == 'epzrjvxtumoc'
121
+ end
122
+ end
123
+
124
+ describe '#delete' do
125
+ it 'should delete the rcfile' do
126
+ path = '/tmp/trc'
127
+ FileUtils.touch(path)
128
+ File.exist?(path).should be_true
129
+ rcfile = RCFile.instance
130
+ rcfile.path = path
131
+ rcfile.delete
132
+ File.exist?(path).should be_false
133
+ end
134
+ end
135
+
136
+ describe '#empty?' do
137
+ context 'when a non-empty file exists' do
138
+ it 'should return false' do
139
+ rcfile = RCFile.instance
140
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
141
+ rcfile.empty?.should be_false
142
+ end
143
+ end
144
+ context 'when file does not exist at path' do
145
+ it 'should return true' do
146
+ rcfile = RCFile.instance
147
+ rcfile.path = File.expand_path('../fixtures/foo', __FILE__)
148
+ rcfile.empty?.should be_true
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#load' do
154
+ context 'when file exists at path' do
155
+ it 'should load data from file' do
156
+ rcfile = RCFile.instance
157
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
158
+ rcfile.load['profiles']['sferik']['abc123']['username'].should == 'sferik'
159
+ end
160
+ end
161
+ context 'when file does not exist at path' do
162
+ it 'should load default structure' do
163
+ rcfile = RCFile.instance
164
+ rcfile.path = File.expand_path('../fixtures/foo', __FILE__)
165
+ rcfile.load.keys.sort.should == ['configuration', 'profiles']
166
+ end
167
+ end
168
+ end
169
+
170
+ describe '#path' do
171
+ it 'should default to ~/.trc' do
172
+ RCFile.instance.path.should == File.join(File.expand_path('~'), '.trc')
173
+ end
174
+ end
175
+
176
+ describe '#path=' do
177
+ it 'should override path' do
178
+ rcfile = RCFile.instance
179
+ rcfile.path = '/tmp/trc'
180
+ rcfile.path.should == '/tmp/trc'
181
+ end
182
+ it 'should reload data' do
183
+ rcfile = RCFile.instance
184
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
185
+ rcfile['sferik']['abc123']['username'].should == 'sferik'
186
+ end
187
+ end
188
+
189
+ describe '#profiles' do
190
+ it 'should return profiles' do
191
+ rcfile = RCFile.instance
192
+ rcfile.path = File.expand_path('../fixtures/.trc', __FILE__)
193
+ rcfile.profiles.keys.should == ['sferik']
194
+ end
195
+ end
196
+
197
+ end
data/t.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ require File.expand_path("../lib/t/version", __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.add_dependency 'actionpack', '~> 3.1'
6
+ gem.add_dependency 'launchy', '~> 2.0'
7
+ gem.add_dependency 'thor', '~> 0.15.0.rc2'
8
+ gem.add_dependency 'twitter', '~> 2.0'
9
+ gem.add_dependency 'oauth', '~> 0.4'
10
+ gem.add_development_dependency 'pry'
11
+ gem.add_development_dependency 'rake'
12
+ gem.add_development_dependency 'rspec'
13
+ gem.add_development_dependency 'simplecov'
14
+ gem.add_development_dependency 'webmock'
15
+ gem.author = "Erik Michaels-Ober"
16
+ gem.bindir = 'bin'
17
+ gem.description = %q{A command-line interface for Twitter.}
18
+ gem.email = 'sferik@gmail.com'
19
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ gem.files = `git ls-files`.split("\n")
21
+ gem.homepage = 'http://github.com/sferik/t'
22
+ gem.name = 't'
23
+ gem.require_paths = ['lib']
24
+ gem.required_rubygems_version = Gem::Requirement.new(">= 1.3.6") if gem.respond_to? :required_rubygems_version=
25
+ gem.summary = %q{CLI for Twitter}
26
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
+ gem.version = T::Version.to_s
28
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: t
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Erik Michaels-Ober
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: actionpack
16
+ requirement: &70208775975760 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70208775975760
25
+ - !ruby/object:Gem::Dependency
26
+ name: launchy
27
+ requirement: &70208775974880 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70208775974880
36
+ - !ruby/object:Gem::Dependency
37
+ name: thor
38
+ requirement: &70208775974380 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.15.0.rc2
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70208775974380
47
+ - !ruby/object:Gem::Dependency
48
+ name: twitter
49
+ requirement: &70208775973660 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70208775973660
58
+ - !ruby/object:Gem::Dependency
59
+ name: oauth
60
+ requirement: &70208775972880 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '0.4'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70208775972880
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: &70208775972500 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70208775972500
80
+ - !ruby/object:Gem::Dependency
81
+ name: rake
82
+ requirement: &70208775994420 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70208775994420
91
+ - !ruby/object:Gem::Dependency
92
+ name: rspec
93
+ requirement: &70208775993580 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *70208775993580
102
+ - !ruby/object:Gem::Dependency
103
+ name: simplecov
104
+ requirement: &70208775993000 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *70208775993000
113
+ - !ruby/object:Gem::Dependency
114
+ name: webmock
115
+ requirement: &70208775992140 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: *70208775992140
124
+ description: A command-line interface for Twitter.
125
+ email: sferik@gmail.com
126
+ executables:
127
+ - t
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - .gitignore
132
+ - .rspec
133
+ - .travis.yml
134
+ - Gemfile
135
+ - LICENSE.md
136
+ - README.md
137
+ - Rakefile
138
+ - bin/t
139
+ - lib/t.rb
140
+ - lib/t/cli.rb
141
+ - lib/t/rcfile.rb
142
+ - lib/t/set.rb
143
+ - lib/t/version.rb
144
+ - spec/fixtures/.trc
145
+ - spec/helper.rb
146
+ - spec/rcfile_spec.rb
147
+ - t.gemspec
148
+ homepage: http://github.com/sferik/t
149
+ licenses: []
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ! '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: 1.3.6
166
+ requirements: []
167
+ rubyforge_project:
168
+ rubygems_version: 1.8.11
169
+ signing_key:
170
+ specification_version: 3
171
+ summary: CLI for Twitter
172
+ test_files:
173
+ - spec/fixtures/.trc
174
+ - spec/helper.rb
175
+ - spec/rcfile_spec.rb
176
+ has_rdoc: