t 0.0.1

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/.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: