dustin-twitter 0.3.2.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.
Files changed (67) hide show
  1. data/History.txt +89 -0
  2. data/License.txt +19 -0
  3. data/Manifest.txt +66 -0
  4. data/README.txt +75 -0
  5. data/Rakefile +4 -0
  6. data/TODO.txt +2 -0
  7. data/bin/twitter +15 -0
  8. data/config/hoe.rb +74 -0
  9. data/config/requirements.rb +17 -0
  10. data/examples/blocks.rb +15 -0
  11. data/examples/direct_messages.rb +26 -0
  12. data/examples/favorites.rb +20 -0
  13. data/examples/friends_followers.rb +25 -0
  14. data/examples/friendships.rb +13 -0
  15. data/examples/location.rb +8 -0
  16. data/examples/replies.rb +26 -0
  17. data/examples/sent_messages.rb +26 -0
  18. data/examples/timeline.rb +33 -0
  19. data/examples/twitter.rb +27 -0
  20. data/examples/verify_credentials.rb +13 -0
  21. data/lib/twitter/base.rb +248 -0
  22. data/lib/twitter/cli/config.rb +9 -0
  23. data/lib/twitter/cli/helpers.rb +97 -0
  24. data/lib/twitter/cli/migrations/20080722194500_create_accounts.rb +13 -0
  25. data/lib/twitter/cli/migrations/20080722194508_create_tweets.rb +16 -0
  26. data/lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb +9 -0
  27. data/lib/twitter/cli/migrations/20080722214606_create_configurations.rb +13 -0
  28. data/lib/twitter/cli/models/account.rb +33 -0
  29. data/lib/twitter/cli/models/configuration.rb +13 -0
  30. data/lib/twitter/cli/models/tweet.rb +20 -0
  31. data/lib/twitter/cli.rb +328 -0
  32. data/lib/twitter/direct_message.rb +22 -0
  33. data/lib/twitter/easy_class_maker.rb +43 -0
  34. data/lib/twitter/rate_limit_status.rb +19 -0
  35. data/lib/twitter/status.rb +22 -0
  36. data/lib/twitter/user.rb +37 -0
  37. data/lib/twitter/version.rb +9 -0
  38. data/lib/twitter.rb +20 -0
  39. data/script/destroy +14 -0
  40. data/script/generate +14 -0
  41. data/script/txt2html +74 -0
  42. data/setup.rb +1585 -0
  43. data/spec/base_spec.rb +109 -0
  44. data/spec/cli/helper_spec.rb +35 -0
  45. data/spec/direct_message_spec.rb +35 -0
  46. data/spec/fixtures/followers.xml +706 -0
  47. data/spec/fixtures/friends.xml +609 -0
  48. data/spec/fixtures/friends_for.xml +584 -0
  49. data/spec/fixtures/friends_lite.xml +192 -0
  50. data/spec/fixtures/friends_timeline.xml +66 -0
  51. data/spec/fixtures/public_timeline.xml +148 -0
  52. data/spec/fixtures/rate_limit_status.xml +7 -0
  53. data/spec/fixtures/status.xml +25 -0
  54. data/spec/fixtures/user.xml +38 -0
  55. data/spec/fixtures/user_timeline.xml +465 -0
  56. data/spec/spec.opts +1 -0
  57. data/spec/spec_helper.rb +8 -0
  58. data/spec/status_spec.rb +40 -0
  59. data/spec/user_spec.rb +42 -0
  60. data/tasks/deployment.rake +41 -0
  61. data/tasks/environment.rake +7 -0
  62. data/tasks/website.rake +17 -0
  63. data/twitter.gemspec +49 -0
  64. data/website/css/common.css +47 -0
  65. data/website/images/terminal_output.png +0 -0
  66. data/website/index.html +138 -0
  67. metadata +176 -0
@@ -0,0 +1,328 @@
1
+ require 'rubygems'
2
+ gem 'main', '>= 2.8.2'
3
+ gem 'highline', '>= 1.4.0'
4
+ gem 'activerecord', '>= 2.1'
5
+ gem 'sqlite3-ruby', '>= 1.2.2'
6
+ require 'main'
7
+ require 'highline/import'
8
+ require 'activerecord'
9
+ require 'sqlite3'
10
+
11
+ HighLine.track_eof = false
12
+ CLI_ROOT = File.expand_path(File.join(File.dirname(__FILE__), 'cli'))
13
+ require CLI_ROOT + '/config'
14
+ require CLI_ROOT + '/helpers'
15
+ Dir[CLI_ROOT + '/models/*.rb'].each { |m| require m }
16
+
17
+ include Twitter::CLI::Helpers
18
+
19
+ Main {
20
+ def run
21
+ puts "twitter [command] --help for usage instructions."
22
+ puts "The available commands are: \n install, uninstall, add, remove, list, change, post, befriend, defriend, follow, leave, d and timeline."
23
+ end
24
+
25
+ mode 'install' do
26
+ description 'Creates the sqlite3 database and runs the migrations.'
27
+ def run
28
+ migrate
29
+ attempt_import
30
+ say 'Twitter installed.'
31
+ end
32
+ end
33
+
34
+ mode 'uninstall' do
35
+ description 'Removes the sqlite3 database. There is no undo for this.'
36
+ def run
37
+ FileUtils.rm(Twitter::CLI::Config[:database]) if File.exists?(Twitter::CLI::Config[:database])
38
+ say 'Twitter gem uninstalled.'
39
+ end
40
+ end
41
+
42
+ mode 'add' do
43
+ description 'Adds a new twitter account to the database. Prompts for username and password.'
44
+ argument('username', 'u') {
45
+ optional
46
+ description 'optional username'
47
+ }
48
+ argument('password', 'p') {
49
+ optional
50
+ description 'optional password'
51
+ }
52
+
53
+ def run
54
+ account = Hash.new
55
+ say "Add New Account:"
56
+
57
+ # allows optional username arg
58
+ if params['username'].given?
59
+ account[:username] = params['username'].value
60
+ else
61
+ account[:username] = ask('Username: ') do |q|
62
+ q.validate = /\S+/
63
+ end
64
+ end
65
+
66
+ # allows optional password arg
67
+ if params['password'].given?
68
+ account[:password] = params['password'].value
69
+ else
70
+ account[:password] = ask("Password (won't be displayed): ") do |q|
71
+ q.echo = false
72
+ q.validate = /\S+/
73
+ end
74
+ end
75
+
76
+ do_work do
77
+ base(account[:username], account[:password]).verify_credentials
78
+ Account.add(account)
79
+ say 'Account added.'
80
+ end
81
+ end
82
+ end
83
+
84
+ mode 'remove' do
85
+ description 'Removes a twitter account from the database. If username provided it removes that username else it prompts with list and asks for which one you would like to remove.'
86
+ argument( 'username' ) {
87
+ optional
88
+ description 'username of account you would like to remove'
89
+ }
90
+
91
+ def run
92
+ do_work do
93
+ if params['username'].given?
94
+ account = Account.find_by_username(params['username'].value)
95
+ else
96
+ Account.find(:all, :order => 'username').each do |a|
97
+ say "#{a.id}. #{a}"
98
+ end
99
+ account_id = ask 'Account to remove (enter number): ' do |q|
100
+ q.validate = /\d+/
101
+ end
102
+ end
103
+
104
+ begin
105
+ account = account_id ? Account.find(account_id) : account
106
+ account_name = account.username
107
+ account.destroy
108
+ Account.set_current(Account.first) if Account.new_active_needed?
109
+ say "#{account_name} has been removed.\n"
110
+ rescue ActiveRecord::RecordNotFound
111
+ say "ERROR: Account could not be found. Try again. \n"
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ mode 'list' do
118
+ description 'Lists all the accounts that have been added and puts a * by the current one that is used for posting, etc.'
119
+ def run
120
+ do_work do
121
+ if Account.count == 0
122
+ say 'No accounts have been added.'
123
+ else
124
+ say 'Account List'
125
+ Account.find(:all, :order => 'username').each do |a|
126
+ say a
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ mode 'change' do
134
+ description 'Changes the current account being used for posting etc. to the username provided. If no username is provided, a list is presented and you can choose the account from there.'
135
+ argument( 'username' ) {
136
+ optional
137
+ description 'username of account you would like to switched to'
138
+ }
139
+
140
+ def run
141
+ do_work do
142
+ if params['username'].given?
143
+ new_current = Account.find_by_username(params['username'].value)
144
+ else
145
+ Account.find(:all, :order => 'username').each do |a|
146
+ say "#{a.id}. #{a}"
147
+ end
148
+ new_current = ask 'Change current account to (enter number): ' do |q|
149
+ q.validate = /\d+/
150
+ end
151
+ end
152
+
153
+ begin
154
+ current = Account.set_current(new_current)
155
+ say "#{current} is now the current account.\n"
156
+ rescue ActiveRecord::RecordNotFound
157
+ say "ERROR: Account could not be found. Try again. \n"
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ mode 'post' do
164
+ description "Posts a message to twitter using the current account. The following are all valid examples from the command line:
165
+ $ twitter post 'my update'
166
+ $ twitter post my update with quotes
167
+ $ echo 'my update from stdin' | twitter post"
168
+ def run
169
+ do_work do
170
+ post = ARGV.size > 1 ? ARGV.join(" ") : ARGV.shift
171
+ say "Sending twitter update"
172
+ finished, status = false, nil
173
+ progress_thread = Thread.new { until finished; print "."; $stdout.flush; sleep 0.5; end; }
174
+ post_thread = Thread.new(binding()) do |b|
175
+ status = base.post(post, :source => Twitter::SourceName)
176
+ finished = true
177
+ end
178
+ post_thread.join
179
+ progress_thread.join
180
+ say "Got it! New twitter created at: #{status.created_at}\n"
181
+ end
182
+ end
183
+ end
184
+
185
+ mode 'befriend' do
186
+ description "Allows you to add a user as a friend"
187
+ argument('username') {
188
+ required
189
+ description 'username or id of twitterrer to befriend'
190
+ }
191
+
192
+ def run
193
+ do_work do
194
+ username = params['username'].value
195
+ base.create_friendship(username)
196
+ say "#{username} has been added as a friend. follow notifications with 'twitter follow #{username}'"
197
+ end
198
+ end
199
+ end
200
+
201
+ mode 'defriend' do
202
+ description "Allows you to remove a user from being a friend"
203
+ argument('username') {
204
+ required
205
+ description 'username or id of twitterrer to defriend'
206
+ }
207
+
208
+ def run
209
+ do_work do
210
+ username = params['username'].value
211
+ base.destroy_friendship(username)
212
+ say "#{username} has been removed from your friends"
213
+ end
214
+ end
215
+ end
216
+
217
+ mode 'follow' do
218
+ description "Allows you to turn on notifications for a user"
219
+ argument('username') {
220
+ required
221
+ description 'username or id of twitterrer to follow'
222
+ }
223
+
224
+ def run
225
+ do_work do
226
+ username = params['username'].value
227
+ base.follow(username)
228
+ say "You are now following notifications from #{username}"
229
+ end
230
+ end
231
+ end
232
+
233
+ mode 'leave' do
234
+ description "Allows you to turn off notifications for a user"
235
+ argument('username') {
236
+ required
237
+ description 'username or id of twitterrer to leave'
238
+ }
239
+
240
+ def run
241
+ do_work do
242
+ username = params['username'].value
243
+ base.leave(username)
244
+ say "You are no longer following notifications from #{username}"
245
+ end
246
+ end
247
+ end
248
+
249
+ mode 'd' do
250
+ description "Allows you to direct message a user. The following are all valid examples from the command line:
251
+ $ twitter d jnunemaker 'yo homeboy'
252
+ $ twitter d jnunemaker yo homeboy
253
+ $ echo 'yo homeboy' | twitter d jnunemaker"
254
+ argument('username') {
255
+ required
256
+ description 'username or id of twitterrer to direct message'
257
+ }
258
+
259
+ def run
260
+ do_work do
261
+ username = params['username'].value
262
+ post = ARGV.size > 1 ? ARGV.join(" ") : ARGV.shift
263
+ base.d(username, post)
264
+ say "Direct message sent to #{username}"
265
+ end
266
+ end
267
+ end
268
+
269
+ mode 'timeline' do
270
+ description "Allows you to view your timeline, your friends or the public one"
271
+ argument( 'timeline' ) {
272
+ description 'the timeline you wish to see (friends, public, me)'
273
+ default 'friends'
274
+ }
275
+ option('force', 'f') {
276
+ description "Ignore since_id and show first page of results even if there aren't new ones"
277
+ }
278
+
279
+ def run
280
+ do_work do
281
+ timeline = params['timeline'].value == 'me' ? 'user' : params['timeline'].value
282
+ options, since_id = {}, Configuration["#{timeline}_since_id"]
283
+ options[:since_id] = since_id if !since_id.blank? && !params['force'].given?
284
+ cache = [:friends, :user].include?(timeline)
285
+ collection = base.timeline(timeline.to_sym, options)
286
+ output_tweets(collection, {:cache => cache, :since_prefix => timeline})
287
+ end
288
+ end
289
+ end
290
+
291
+ mode 'replies' do
292
+ description 'Allows you to view all @replies at you'
293
+ option('force', 'f') {
294
+ description "Ignore since_id and show first page of replies even if there aren't new ones"
295
+ }
296
+
297
+ def run
298
+ do_work do
299
+ options, since_id = {}, Configuration["replies_since_id"]
300
+ options[:since_id] = since_id if !since_id.blank? && !params['force'].given?
301
+ collection = base.replies(options)
302
+ output_tweets(collection, {:since_prefix => 'replies'})
303
+ end
304
+ end
305
+ end
306
+
307
+ mode 'clear_config' do
308
+ def run
309
+ do_work do
310
+ count = Configuration.count
311
+ Configuration.destroy_all
312
+ say("#{count} configuration entries cleared.")
313
+ end
314
+ end
315
+ end
316
+
317
+ mode 'open' do
318
+ description 'Opens the given twitter user in a browser window'
319
+ argument('username') {
320
+ required
321
+ description "username or id of twitterrer who's page you would like to see"
322
+ }
323
+
324
+ def run
325
+ `open http://twitter.com/#{params['username'].value}`
326
+ end
327
+ end
328
+ }
@@ -0,0 +1,22 @@
1
+ module Twitter
2
+ class DirectMessage
3
+ include EasyClassMaker
4
+
5
+ attributes :id, :text, :sender_id, :recipient_id, :created_at, :sender_screen_name, :recipient_screen_name
6
+
7
+ class << self
8
+ # Creates a new status from a piece of xml
9
+ def new_from_xml(xml)
10
+ DirectMessage.new do |d|
11
+ d.id = (xml).at('id').innerHTML
12
+ d.text = (xml).get_elements_by_tag_name('text').innerHTML
13
+ d.sender_id = (xml).at('sender_id').innerHTML
14
+ d.recipient_id = (xml).at('recipient_id').innerHTML
15
+ d.created_at = (xml).at('created_at').innerHTML
16
+ d.sender_screen_name = (xml).at('sender_screen_name').innerHTML
17
+ d.recipient_screen_name = (xml).at('recipient_screen_name').innerHTML
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ # This is pretty much just a macro for creating a class that allows
2
+ # using a block to initialize stuff and to define getters and setters
3
+ # really quickly.
4
+ module Twitter
5
+ module EasyClassMaker
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ # creates the attributes class variable and creates each attribute's accessor methods
13
+ def attributes(*attrs)
14
+ @@attributes = attrs
15
+ @@attributes.each { |a| attr_accessor a }
16
+ end
17
+
18
+ # read method for attributes class variable
19
+ def self.attributes; @@attributes end
20
+ end
21
+
22
+ # allows for any class that includes this to use a block to initialize
23
+ # variables instead of assigning each one seperately
24
+ #
25
+ # Example:
26
+ #
27
+ # instead of...
28
+ #
29
+ # s = Status.new
30
+ # s.foo = 'thing'
31
+ # s.bar = 'another thing'
32
+ #
33
+ # you can ...
34
+ #
35
+ # Status.new do |s|
36
+ # s.foo = 'thing'
37
+ # s.bar = 'another thing'
38
+ # end
39
+ def initialize
40
+ yield self if block_given?
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module Twitter
2
+ class RateLimitStatus
3
+ include EasyClassMaker
4
+
5
+ attributes :reset_time_in_seconds, :reset_time, :remaining_hits, :hourly_limit
6
+
7
+ class << self
8
+ # Creates a new rate limi status from a piece of xml
9
+ def new_from_xml(xml)
10
+ RateLimitStatus.new do |s|
11
+ s.reset_time_in_seconds = xml.at('reset-time-in-seconds').inner_html.to_i
12
+ s.reset_time = Time.parse xml.at('reset-time').inner_html
13
+ s.remaining_hits = xml.at('remaining-hits').inner_html.to_i
14
+ s.hourly_limit = xml.at('hourly-limit').inner_html.to_i
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module Twitter
2
+ class Status
3
+ include EasyClassMaker
4
+
5
+ attributes :created_at, :id, :text, :user, :source, :truncated, :in_reply_to_status_id, :in_reply_to_user_id, :favorited
6
+
7
+ # Creates a new status from a piece of xml
8
+ def self.new_from_xml(xml)
9
+ s = new
10
+ s.id = (xml).at('id').innerHTML
11
+ s.created_at = (xml).at('created_at').innerHTML
12
+ s.text = (xml).get_elements_by_tag_name('text').innerHTML
13
+ s.source = (xml).at('source').innerHTML
14
+ s.truncated = (xml).at('truncated').innerHTML == 'false' ? false : true
15
+ s.favorited = (xml).at('favorited').innerHTML == 'false' ? false : true
16
+ s.in_reply_to_status_id = (xml).at('in_reply_to_status_id').innerHTML
17
+ s.in_reply_to_user_id = (xml).at('in_reply_to_user_id').innerHTML
18
+ s.user = User.new_from_xml(xml.at('user')) if (xml).at('user')
19
+ s
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module Twitter
2
+ class User
3
+ include EasyClassMaker
4
+
5
+ attributes :id, :name, :screen_name, :status, :location, :description, :url,
6
+ :profile_image_url, :profile_background_color, :profile_text_color, :profile_link_color,
7
+ :profile_sidebar_fill_color, :profile_sidebar_border_color, :friends_count, :followers_count,
8
+ :favourites_count, :statuses_count, :utc_offset , :protected
9
+
10
+ # Creates a new user from a piece of xml
11
+ def self.new_from_xml(xml)
12
+ u = new
13
+ u.id = (xml).at('id').innerHTML
14
+ u.name = (xml).at('name').innerHTML
15
+ u.screen_name = (xml).at('screen_name').innerHTML
16
+ u.location = (xml).at('location').innerHTML
17
+ u.description = (xml).at('description').innerHTML
18
+ u.url = (xml).at('url').innerHTML
19
+ u.profile_image_url = (xml).at('profile_image_url').innerHTML
20
+
21
+ # optional, not always present
22
+ u.profile_background_color = (xml).at('profile_background_color').innerHTML if (xml).at('profile_background_color')
23
+ u.profile_text_color = (xml).at('profile_text_color').innerHTML if (xml).at('profile_text_color')
24
+ u.profile_link_color = (xml).at('profile_link_color').innerHTML if (xml).at('profile_link_color')
25
+ u.profile_sidebar_fill_color = (xml).at('profile_sidebar_fill_color').innerHTML if (xml).at('profile_sidebar_fill_color')
26
+ u.profile_sidebar_border_color = (xml).at('profile_sidebar_border_color').innerHTML if (xml).at('profile_sidebar_border_color')
27
+ u.friends_count = (xml).at('friends_count').innerHTML if (xml).at('friends_count')
28
+ u.followers_count = (xml).at('followers_count').innerHTML if (xml).at('followers_count')
29
+ u.favourites_count = (xml).at('favourites_count').innerHTML if (xml).at('favourites_count')
30
+ u.statuses_count = (xml).at('statuses_count').innerHTML if (xml).at('statuses_count')
31
+ u.utc_offset = (xml).at('utc_offset').innerHTML if (xml).at('utc_offset')
32
+ u.protected = (xml).at('protected').innerHTML == 'false' ? false : true if (xml).at('protected')
33
+ u.status = Status.new_from_xml(xml.at('status')) if (xml).at('status')
34
+ u
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module Twitter #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 3
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/twitter.rb ADDED
@@ -0,0 +1,20 @@
1
+ %w(uri cgi net/http yaml rubygems hpricot active_support).each { |f| require f }
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__)))
4
+ require 'twitter/version'
5
+ require 'twitter/easy_class_maker'
6
+ require 'twitter/base'
7
+ require 'twitter/user'
8
+ require 'twitter/status'
9
+ require 'twitter/direct_message'
10
+ require 'twitter/rate_limit_status'
11
+
12
+ module Twitter
13
+ class Unavailable < StandardError; end
14
+ class CantConnect < StandardError; end
15
+ class BadResponse < StandardError; end
16
+ class UnknownTimeline < ArgumentError; end
17
+ class RateExceeded < StandardError; end
18
+
19
+ SourceName = 'twittergem'
20
+ end
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/script/txt2html ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ begin
5
+ require 'newgem'
6
+ rescue LoadError
7
+ puts "\n\nGenerating the website requires the newgem RubyGem"
8
+ puts "Install: gem install newgem\n\n"
9
+ exit(1)
10
+ end
11
+ require 'redcloth'
12
+ require 'syntax/convertors/html'
13
+ require 'erb'
14
+ require File.dirname(__FILE__) + '/../lib/twitter/version.rb'
15
+
16
+ version = Twitter.git::VERSION::STRING
17
+ download = 'http://rubyforge.org/projects/twitter'
18
+
19
+ class Fixnum
20
+ def ordinal
21
+ # teens
22
+ return 'th' if (10..19).include?(self % 100)
23
+ # others
24
+ case self % 10
25
+ when 1: return 'st'
26
+ when 2: return 'nd'
27
+ when 3: return 'rd'
28
+ else return 'th'
29
+ end
30
+ end
31
+ end
32
+
33
+ class Time
34
+ def pretty
35
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
36
+ end
37
+ end
38
+
39
+ def convert_syntax(syntax, source)
40
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
41
+ end
42
+
43
+ if ARGV.length >= 1
44
+ src, template = ARGV
45
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
46
+
47
+ else
48
+ puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
49
+ exit!
50
+ end
51
+
52
+ template = ERB.new(File.open(template).read)
53
+
54
+ title = nil
55
+ body = nil
56
+ File.open(src) do |fsrc|
57
+ title_text = fsrc.readline
58
+ body_text = fsrc.read
59
+ syntax_items = []
60
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
61
+ ident = syntax_items.length
62
+ element, syntax, source = $1, $2, $3
63
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
64
+ "syntax-temp-#{ident}"
65
+ }
66
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
67
+ body = RedCloth.new(body_text).to_html
68
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
69
+ end
70
+ stat = File.stat(src)
71
+ created = stat.ctime
72
+ modified = stat.mtime
73
+
74
+ $stdout << template.result(binding)