jnunemaker-twitter 0.2.8

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 (64) hide show
  1. data/History.txt +76 -0
  2. data/License.txt +19 -0
  3. data/Manifest.txt +63 -0
  4. data/README.txt +75 -0
  5. data/Rakefile +11 -0
  6. data/bin/twitter +15 -0
  7. data/config/hoe.rb +73 -0
  8. data/config/requirements.rb +17 -0
  9. data/examples/blocks.rb +15 -0
  10. data/examples/direct_messages.rb +26 -0
  11. data/examples/favorites.rb +20 -0
  12. data/examples/friends_followers.rb +25 -0
  13. data/examples/friendships.rb +13 -0
  14. data/examples/location.rb +8 -0
  15. data/examples/replies.rb +26 -0
  16. data/examples/sent_messages.rb +26 -0
  17. data/examples/timeline.rb +33 -0
  18. data/examples/twitter.rb +27 -0
  19. data/examples/verify_credentials.rb +13 -0
  20. data/lib/twitter/base.rb +249 -0
  21. data/lib/twitter/cli/config.rb +9 -0
  22. data/lib/twitter/cli/helpers.rb +46 -0
  23. data/lib/twitter/cli/migrations/20080722194500_create_accounts.rb +13 -0
  24. data/lib/twitter/cli/migrations/20080722194508_create_tweets.rb +16 -0
  25. data/lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb +9 -0
  26. data/lib/twitter/cli/migrations/20080722214606_create_configurations.rb +13 -0
  27. data/lib/twitter/cli/models/account.rb +21 -0
  28. data/lib/twitter/cli/models/configuration.rb +13 -0
  29. data/lib/twitter/cli/models/tweet.rb +20 -0
  30. data/lib/twitter/cli.rb +297 -0
  31. data/lib/twitter/direct_message.rb +22 -0
  32. data/lib/twitter/easy_class_maker.rb +43 -0
  33. data/lib/twitter/rate_limit_status.rb +19 -0
  34. data/lib/twitter/status.rb +22 -0
  35. data/lib/twitter/user.rb +37 -0
  36. data/lib/twitter/version.rb +9 -0
  37. data/lib/twitter.rb +20 -0
  38. data/script/destroy +14 -0
  39. data/script/generate +14 -0
  40. data/script/txt2html +74 -0
  41. data/setup.rb +1585 -0
  42. data/spec/base_spec.rb +109 -0
  43. data/spec/direct_message_spec.rb +35 -0
  44. data/spec/fixtures/followers.xml +706 -0
  45. data/spec/fixtures/friends.xml +609 -0
  46. data/spec/fixtures/friends_for.xml +584 -0
  47. data/spec/fixtures/friends_lite.xml +192 -0
  48. data/spec/fixtures/friends_timeline.xml +66 -0
  49. data/spec/fixtures/public_timeline.xml +148 -0
  50. data/spec/fixtures/rate_limit_status.xml +7 -0
  51. data/spec/fixtures/status.xml +25 -0
  52. data/spec/fixtures/user.xml +38 -0
  53. data/spec/fixtures/user_timeline.xml +465 -0
  54. data/spec/spec.opts +1 -0
  55. data/spec/spec_helper.rb +8 -0
  56. data/spec/status_spec.rb +40 -0
  57. data/spec/user_spec.rb +42 -0
  58. data/tasks/deployment.rake +34 -0
  59. data/tasks/environment.rake +7 -0
  60. data/tasks/website.rake +17 -0
  61. data/twitter.gemspec +23 -0
  62. data/website/css/common.css +47 -0
  63. data/website/index.html +135 -0
  64. metadata +136 -0
@@ -0,0 +1,297 @@
1
+ require 'rubygems'
2
+ gem 'main', '>= 2.8.2'
3
+ gem 'highline', '>= 1.4.0'
4
+ gem 'activerecord', '>= 2.1.0'
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
+ say 'Twitter installed.'
30
+ end
31
+ end
32
+
33
+ mode 'uninstall' do
34
+ description 'Removes the sqlite3 database. There is no undo for this.'
35
+ def run
36
+ FileUtils.rm(Twitter::CLI::Config[:database])
37
+ say 'Twitter gem uninstalled.'
38
+ end
39
+ end
40
+
41
+ mode 'add' do
42
+ description 'Adds a new twitter account to the database. Prompts for username and password.'
43
+ def run
44
+ account = Hash.new
45
+ say "Add New Account:"
46
+
47
+ account[:username] = ask('Username: ') do |q|
48
+ q.validate = /\S+/
49
+ end
50
+
51
+ account[:password] = ask("Password (won't be displayed): ") do |q|
52
+ q.echo = false
53
+ q.validate = /\S+/
54
+ end
55
+
56
+ do_work do
57
+ base(account[:username], account[:password]).verify_credentials
58
+ account[:current] = Account.current.count > 0 ? false : true
59
+ Account.create(account)
60
+ say 'Account added.'
61
+ end
62
+ end
63
+ end
64
+
65
+ mode 'remove' do
66
+ 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.'
67
+ argument( 'username' ) {
68
+ optional
69
+ description 'username of account you would like to remove'
70
+ }
71
+
72
+ def run
73
+ do_work do
74
+ if params['username'].given?
75
+ account = Account.find_by_username(params['username'].value)
76
+ else
77
+ Account.find(:all, :order => 'username').each do |a|
78
+ say "#{a.id}. #{a}"
79
+ end
80
+ account_id = ask 'Account to remove (enter number): ' do |q|
81
+ q.validate = /\d+/
82
+ end
83
+ end
84
+
85
+ begin
86
+ account = account_id ? Account.find(account_id) : account
87
+ account_name = account.username
88
+ account.destroy
89
+ say "#{account_name} has been removed.\n"
90
+ rescue ActiveRecord::RecordNotFound
91
+ say "ERROR: Account could not be found. Try again. \n"
92
+ end
93
+
94
+ end
95
+ end
96
+ end
97
+
98
+ mode 'list' do
99
+ description 'Lists all the accounts that have been added and puts a * by the current one that is used for posting, etc.'
100
+ def run
101
+ do_work do
102
+ if Account.count == 0
103
+ say 'No accounts have been added.'
104
+ else
105
+ say 'Account List'
106
+ Account.find(:all, :order => 'username').each do |a|
107
+ say a
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ mode 'change' do
115
+ 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.'
116
+ argument( 'username' ) {
117
+ optional
118
+ description 'username of account you would like to switched to'
119
+ }
120
+
121
+ def run
122
+ do_work do
123
+ if params['username'].given?
124
+ new_current = Account.find_by_username(params['username'].value)
125
+ else
126
+ Account.find(:all, :order => 'username').each do |a|
127
+ say "#{a.id}. #{a}"
128
+ end
129
+ new_current = ask 'Change current account to (enter number): ' do |q|
130
+ q.validate = /\d+/
131
+ end
132
+ end
133
+
134
+ begin
135
+ current = Account.set_current(new_current)
136
+ say "#{current} is now the current account.\n"
137
+ rescue ActiveRecord::RecordNotFound
138
+ say "ERROR: Account could not be found. Try again. \n"
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ mode 'post' do
145
+ description "Posts a message to twitter using the current account. The following are all valid examples from the command line:
146
+ $ twitter post 'my update'
147
+ $ twitter post my update with quotes
148
+ $ echo 'my update from stdin' | twitter post"
149
+ def run
150
+ do_work do
151
+ post = ARGV.size > 1 ? ARGV.join(" ") : ARGV.shift
152
+ say "Sending twitter update"
153
+ finished, status = false, nil
154
+ progress_thread = Thread.new { until finished; print "."; $stdout.flush; sleep 0.5; end; }
155
+ post_thread = Thread.new(binding()) do |b|
156
+ status = base.post(post, :source => Twitter::SourceName)
157
+ finished = true
158
+ end
159
+ post_thread.join
160
+ progress_thread.join
161
+ say "Got it! New twitter created at: #{status.created_at}\n"
162
+ end
163
+ end
164
+ end
165
+
166
+ mode 'befriend' do
167
+ description "Allows you to add a user as a friend"
168
+ argument('username') {
169
+ required
170
+ description 'username or id of twitterrer to befriend'
171
+ }
172
+
173
+ def run
174
+ do_work do
175
+ username = params['username'].value
176
+ base.create_friendship(username)
177
+ say "#{username} has been added as a friend. follow notifications with 'twitter follow #{username}'"
178
+ end
179
+ end
180
+ end
181
+
182
+ mode 'defriend' do
183
+ description "Allows you to remove a user from being a friend"
184
+ argument('username') {
185
+ required
186
+ description 'username or id of twitterrer to defriend'
187
+ }
188
+
189
+ def run
190
+ do_work do
191
+ username = params['username'].value
192
+ base.destroy_friendship(username)
193
+ say "#{username} has been removed from your friends"
194
+ end
195
+ end
196
+ end
197
+
198
+ mode 'follow' do
199
+ description "Allows you to turn on notifications for a user"
200
+ argument('username') {
201
+ required
202
+ description 'username or id of twitterrer to follow'
203
+ }
204
+
205
+ def run
206
+ do_work do
207
+ username = params['username'].value
208
+ base.follow(username)
209
+ say "You are now following notifications from #{username}"
210
+ end
211
+ end
212
+ end
213
+
214
+ mode 'leave' do
215
+ description "Allows you to turn off notifications for a user"
216
+ argument('username') {
217
+ required
218
+ description 'username or id of twitterrer to leave'
219
+ }
220
+
221
+ def run
222
+ do_work do
223
+ username = params['username'].value
224
+ base.leave(username)
225
+ say "You are no longer following notifications from #{username}"
226
+ end
227
+ end
228
+ end
229
+
230
+ mode 'd' do
231
+ description "Allows you to direct message a user. The following are all valid examples from the command line:
232
+ $ twitter d jnunemaker 'yo homeboy'
233
+ $ twitter d jnunemaker yo homeboy
234
+ $ echo 'yo homeboy' | twitter d jnunemaker"
235
+ argument('username') {
236
+ required
237
+ description 'username or id of twitterrer to direct message'
238
+ }
239
+
240
+ def run
241
+ do_work do
242
+ username = params['username'].value
243
+ post = ARGV.size > 1 ? ARGV.join(" ") : ARGV.shift
244
+ base.d(username, post)
245
+ say "Direct message sent to #{username}"
246
+ end
247
+ end
248
+ end
249
+
250
+ mode 'timeline' do
251
+ description "Allows you to view your timeline, your friends or the public one"
252
+ argument( 'timeline' ) {
253
+ description 'the timeline you wish to see (friends, public, me)'
254
+ default 'friends'
255
+ }
256
+
257
+ def run
258
+ do_work do
259
+ timeline = params['timeline'].value == 'me' ? 'user' : params['timeline'].value
260
+ options, since_id = {}, Configuration["#{timeline}_last_id"]
261
+ options[:since_id] = since_id unless since_id.blank?
262
+ statuses = base.timeline(timeline.to_sym, options)
263
+ username_length = statuses.collect { |s| s.user.screen_name }.max { |a,b| a.length <=> b.length }.length rescue 0
264
+ if statuses.size > 0
265
+ statuses.each do |s|
266
+ Tweet.create_from_tweet(current_account, s) if timeline != :public
267
+ say "#{CGI::unescapeHTML(s.user.screen_name.ljust(username_length+1))}: #{CGI::unescapeHTML(s.text)} on #{Time.parse(s.created_at).strftime('%b %d at %l:%M%P')}"
268
+ end
269
+ Configuration["#{timeline}_last_id"] = statuses.first.id
270
+ else
271
+ say 'Nothing new since your last check'
272
+ end
273
+ end
274
+ end
275
+ end
276
+
277
+ mode 'replies' do
278
+ description 'Allows you to view all @replies at you'
279
+
280
+ def run
281
+ do_work do
282
+ options, since_id = {}, Configuration["replies_since_id"]
283
+ options[:since_id] = since_id if !since_id.blank?
284
+ replies = base.replies(options)
285
+ username_length = replies.collect { |s| s.user.screen_name }.max { |a,b| a.length <=> b.length }.length rescue 0
286
+ if replies.size > 0
287
+ replies.each do |s|
288
+ say "#{CGI::unescapeHTML(s.user.screen_name.ljust(username_length+1))}: #{CGI::unescapeHTML(s.text)} on #{Time.parse(s.created_at).strftime('%b %d at %l:%M%P')}"
289
+ end
290
+ Configuration["replies_since_id"] = replies.first.id
291
+ else
292
+ say 'No new replies since your last check'
293
+ end
294
+ end
295
+ end
296
+ end
297
+ }
@@ -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 = 0
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)