rethoth 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/bin/thoth +233 -0
  4. data/lib/proto/config.ru +45 -0
  5. data/lib/proto/thoth.conf.sample +206 -0
  6. data/lib/thoth/cache.rb +53 -0
  7. data/lib/thoth/config.rb +158 -0
  8. data/lib/thoth/controller/admin.rb +75 -0
  9. data/lib/thoth/controller/api/comment.rb +73 -0
  10. data/lib/thoth/controller/api/page.rb +134 -0
  11. data/lib/thoth/controller/api/post.rb +122 -0
  12. data/lib/thoth/controller/api/tag.rb +59 -0
  13. data/lib/thoth/controller/archive.rb +50 -0
  14. data/lib/thoth/controller/comment.rb +173 -0
  15. data/lib/thoth/controller/main.rb +193 -0
  16. data/lib/thoth/controller/media.rb +172 -0
  17. data/lib/thoth/controller/page.rb +167 -0
  18. data/lib/thoth/controller/post.rb +310 -0
  19. data/lib/thoth/controller/search.rb +86 -0
  20. data/lib/thoth/controller/tag.rb +107 -0
  21. data/lib/thoth/controller.rb +48 -0
  22. data/lib/thoth/errors.rb +35 -0
  23. data/lib/thoth/helper/admin.rb +86 -0
  24. data/lib/thoth/helper/cookie.rb +45 -0
  25. data/lib/thoth/helper/error.rb +122 -0
  26. data/lib/thoth/helper/pagination.rb +131 -0
  27. data/lib/thoth/helper/wiki.rb +77 -0
  28. data/lib/thoth/helper/ysearch.rb +89 -0
  29. data/lib/thoth/importer/pants.rb +81 -0
  30. data/lib/thoth/importer/poseidon.rb +54 -0
  31. data/lib/thoth/importer/thoth.rb +103 -0
  32. data/lib/thoth/importer.rb +131 -0
  33. data/lib/thoth/layout/default.rhtml +47 -0
  34. data/lib/thoth/middleware/minify.rb +82 -0
  35. data/lib/thoth/migrate/001_create_schema.rb +108 -0
  36. data/lib/thoth/migrate/002_add_media_size.rb +37 -0
  37. data/lib/thoth/migrate/003_add_post_draft.rb +38 -0
  38. data/lib/thoth/migrate/004_add_comment_email.rb +37 -0
  39. data/lib/thoth/migrate/005_add_page_position.rb +37 -0
  40. data/lib/thoth/migrate/006_add_comment_close_delete.rb +43 -0
  41. data/lib/thoth/migrate/007_add_comment_summary.rb +37 -0
  42. data/lib/thoth/model/comment.rb +216 -0
  43. data/lib/thoth/model/media.rb +87 -0
  44. data/lib/thoth/model/page.rb +204 -0
  45. data/lib/thoth/model/post.rb +262 -0
  46. data/lib/thoth/model/tag.rb +80 -0
  47. data/lib/thoth/model/tags_posts_map.rb +34 -0
  48. data/lib/thoth/monkeypatch/sequel/model/errors.rb +37 -0
  49. data/lib/thoth/plugin/thoth_delicious.rb +105 -0
  50. data/lib/thoth/plugin/thoth_flickr.rb +86 -0
  51. data/lib/thoth/plugin/thoth_pinboard.rb +98 -0
  52. data/lib/thoth/plugin/thoth_tags.rb +68 -0
  53. data/lib/thoth/plugin/thoth_twitter.rb +175 -0
  54. data/lib/thoth/plugin.rb +59 -0
  55. data/lib/thoth/public/css/admin.css +223 -0
  56. data/lib/thoth/public/css/thoth.css +592 -0
  57. data/lib/thoth/public/images/admin-sprite.png +0 -0
  58. data/lib/thoth/public/images/thoth-sprite.png +0 -0
  59. data/lib/thoth/public/js/admin/comments.js +116 -0
  60. data/lib/thoth/public/js/admin/name.js +244 -0
  61. data/lib/thoth/public/js/admin/tagcomplete.js +332 -0
  62. data/lib/thoth/public/js/lazyload-min.js +4 -0
  63. data/lib/thoth/public/js/thoth.js +203 -0
  64. data/lib/thoth/public/robots.txt +5 -0
  65. data/lib/thoth/version.rb +37 -0
  66. data/lib/thoth/view/admin/index.rhtml +1 -0
  67. data/lib/thoth/view/admin/login.rhtml +23 -0
  68. data/lib/thoth/view/admin/toolbar.rhtml +117 -0
  69. data/lib/thoth/view/admin/welcome.rhtml +58 -0
  70. data/lib/thoth/view/archive/index.rhtml +24 -0
  71. data/lib/thoth/view/comment/comment.rhtml +47 -0
  72. data/lib/thoth/view/comment/delete.rhtml +15 -0
  73. data/lib/thoth/view/comment/form.rhtml +81 -0
  74. data/lib/thoth/view/comment/index.rhtml +68 -0
  75. data/lib/thoth/view/comment/list.rhtml +48 -0
  76. data/lib/thoth/view/media/delete.rhtml +15 -0
  77. data/lib/thoth/view/media/edit.rhtml +12 -0
  78. data/lib/thoth/view/media/form.rhtml +7 -0
  79. data/lib/thoth/view/media/list.rhtml +35 -0
  80. data/lib/thoth/view/media/media.rhtml +44 -0
  81. data/lib/thoth/view/media/new.rhtml +7 -0
  82. data/lib/thoth/view/page/delete.rhtml +15 -0
  83. data/lib/thoth/view/page/edit.rhtml +15 -0
  84. data/lib/thoth/view/page/form.rhtml +57 -0
  85. data/lib/thoth/view/page/index.rhtml +9 -0
  86. data/lib/thoth/view/page/list.rhtml +49 -0
  87. data/lib/thoth/view/page/new.rhtml +15 -0
  88. data/lib/thoth/view/post/comments.rhtml +12 -0
  89. data/lib/thoth/view/post/compact.rhtml +48 -0
  90. data/lib/thoth/view/post/delete.rhtml +15 -0
  91. data/lib/thoth/view/post/edit.rhtml +15 -0
  92. data/lib/thoth/view/post/form.rhtml +83 -0
  93. data/lib/thoth/view/post/index.rhtml +48 -0
  94. data/lib/thoth/view/post/list.rhtml +61 -0
  95. data/lib/thoth/view/post/new.rhtml +15 -0
  96. data/lib/thoth/view/post/tiny.rhtml +4 -0
  97. data/lib/thoth/view/search/index.rhtml +45 -0
  98. data/lib/thoth/view/tag/index.rhtml +34 -0
  99. data/lib/thoth/view/thoth/css.rhtml +9 -0
  100. data/lib/thoth/view/thoth/footer.rhtml +15 -0
  101. data/lib/thoth/view/thoth/header.rhtml +23 -0
  102. data/lib/thoth/view/thoth/index.rhtml +11 -0
  103. data/lib/thoth/view/thoth/js.rhtml +6 -0
  104. data/lib/thoth/view/thoth/sidebar.rhtml +38 -0
  105. data/lib/thoth/view/thoth/util/pager.rhtml +23 -0
  106. data/lib/thoth/view/thoth/util/simple_pager.rhtml +15 -0
  107. data/lib/thoth/view/thoth/util/table_sortheader.rhtml +20 -0
  108. data/lib/thoth.rb +394 -0
  109. metadata +409 -0
@@ -0,0 +1,262 @@
1
+ #--
2
+ # Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ module Thoth
30
+ class Post < Sequel::Model
31
+ include Thoth::Helper::Wiki
32
+
33
+ plugin :hook_class_methods
34
+ plugin :validation_helpers
35
+
36
+ one_to_many :tags_posts_map, :class => 'Thoth::TagsPostsMap'
37
+ many_to_many :tags, :class => 'Thoth::Tag', :join_table => :tags_posts_map,
38
+ :order => :name
39
+
40
+ before_create do
41
+ self.created_at = Time.now
42
+ end
43
+
44
+ before_destroy do
45
+ TagsPostsMap.filter(:post_id => id).delete
46
+ Comment.filter(:post_id => id).delete
47
+ end
48
+
49
+ before_save do
50
+ self.updated_at = Time.now
51
+ end
52
+
53
+ #--
54
+ # Class Methods
55
+ #++
56
+
57
+ # Gets the published post with the specified name, where _name_ can be
58
+ # either a name or an id. Does not return drafts.
59
+ def self.get(name)
60
+ return Post[:id => name, :is_draft => false] if name.is_a?(Numeric)
61
+
62
+ name = name.to_s.downcase
63
+ name =~ /^\d+$/ ? Post[:id => name, :is_draft => false] :
64
+ Post[:name => name, :is_draft => false]
65
+ end
66
+
67
+ # Returns true if the specified post name is already taken or is a reserved
68
+ # name.
69
+ def self.name_unique?(name)
70
+ !PostController.methods.include?(name) &&
71
+ !PostController.instance_methods.include?(name) &&
72
+ !Post[:name => name.to_s.downcase]
73
+ end
74
+
75
+ # Returns true if the specified post name consists of valid characters and
76
+ # is not too long or too short.
77
+ def self.name_valid?(name)
78
+ !!(name =~ /^[0-9a-z_-]{1,64}$/i) && !(name =~ /^[0-9]+$/)
79
+ end
80
+
81
+ # Gets a paginated dataset of recent published posts sorted in reverse order
82
+ # by creation time. Does not return drafts.
83
+ def self.recent(page = 1, limit = 10)
84
+ filter(:is_draft => false).reverse_order(:created_at).paginate(page,
85
+ limit)
86
+ end
87
+
88
+ # Gets a paginated dataset of recent draft posts sorted in reverse order
89
+ # by creation time. Does not return published posts.
90
+ def self.recent_drafts(page = 1, limit = 10)
91
+ filter(:is_draft => true).reverse_order(:created_at).paginate(page,
92
+ limit)
93
+ end
94
+
95
+ # Returns a valid, unique post name based on the specified title. If the
96
+ # title is empty or cannot be converted into a valid name, an empty string
97
+ # will be returned.
98
+ def self.suggest_name(title)
99
+ index = 1
100
+
101
+ # Remove HTML entities and non-alphanumeric characters, replace spaces
102
+ # with hyphens, and truncate the name at 64 characters.
103
+ name = title.to_s.strip.downcase.gsub(/&[^\s;]+;/, '_').
104
+ gsub(/[^\s0-9a-z-]/, '').gsub(/\s+/, '-')[0..63]
105
+
106
+ # Strip off any trailing non-alphanumeric characters.
107
+ name.gsub!(/[_-]+$/, '')
108
+
109
+ return '' if name.empty?
110
+
111
+ # If the name consists solely of numeric characters, add an alpha
112
+ # character to prevent name/id ambiguity.
113
+ name += '_' unless name =~ /[a-z_-]/
114
+
115
+ # Ensure that the name doesn't conflict with any methods on the Post
116
+ # controller and that no two posts have the same name.
117
+ until self.name_unique?(name)
118
+ if name[-1] == index
119
+ name[-1] = (index += 1).to_s
120
+ else
121
+ name = name[0..62] if name.size >= 64
122
+ name += (index += 1).to_s
123
+ end
124
+ end
125
+
126
+ return name
127
+ end
128
+
129
+ #--
130
+ # Instance Methods
131
+ #++
132
+
133
+ # Gets the Atom feed URL for this post.
134
+ def atom_url
135
+ Config.site['url'].chomp('/') + PostController.r(:atom, name).to_s
136
+ end
137
+
138
+ def body=(body)
139
+ self[:body] = body.strip
140
+ self[:body_rendered] = RedCloth.new(wiki_to_html(body.dup.strip)).to_html
141
+ end
142
+
143
+ # Gets a dataset of visible comments attached to this post, ordered by
144
+ # creation time.
145
+ def comments
146
+ @comments ||= Comment.filter(:post_id => id, :deleted => false).order(:created_at)
147
+ end
148
+
149
+ # Gets the creation time of this post. If _format_ is provided, the time
150
+ # will be returned as a formatted String. See Time.strftime for details.
151
+ def created_at(format = nil)
152
+ if new?
153
+ format ? Time.now.strftime(format) : Time.now
154
+ else
155
+ format ? self[:created_at].strftime(format) : self[:created_at]
156
+ end
157
+ end
158
+
159
+ def name=(name)
160
+ self[:name] = name.to_s.strip.downcase unless name.nil?
161
+ end
162
+
163
+ # Gets an Array of tags attached to this post, ordered by name.
164
+ def tags
165
+ if new?
166
+ @fake_tags || []
167
+ else
168
+ @tags ||= tags_dataset.all
169
+ end
170
+ end
171
+
172
+ def tags=(tag_names)
173
+ if tag_names.is_a?(String)
174
+ tag_names = tag_names.split(',', 64)
175
+ elsif !tag_names.is_a?(Array)
176
+ raise ArgumentError, "Expected String or Array, got #{tag_names.class}"
177
+ end
178
+
179
+ tag_names = tag_names.map{|n| n.strip.downcase}.uniq.delete_if{|n|
180
+ n.empty?}
181
+
182
+ if new?
183
+ # This Post hasn't been saved yet, so instead of attaching actual tags
184
+ # to it, we'll create a bunch of fake tags just for the preview. We
185
+ # won't create the real ones until the Post is saved.
186
+ @fake_tags = []
187
+
188
+ tag_names.each {|name| @fake_tags << Tag.new(:name => name)}
189
+ @fake_tags.sort! {|a, b| a.name <=> b.name }
190
+
191
+ return @fake_tags
192
+ else
193
+ real_tags = []
194
+
195
+ # First delete any existing tag mappings for this post.
196
+ TagsPostsMap.filter(:post_id => id).delete
197
+
198
+ # Create new tags and new mappings.
199
+ tag_names.each do |name|
200
+ tag = Tag.find_or_create(:name => name)
201
+ real_tags << tag
202
+ TagsPostsMap.create(:post_id => id, :tag_id => tag.id)
203
+ end
204
+
205
+ return real_tags
206
+ end
207
+ end
208
+
209
+ def title=(title)
210
+ title.strip!
211
+
212
+ # Set the post's name if it isn't already set.
213
+ if self[:name].nil? || self[:name].empty?
214
+ self[:name] = Post.suggest_name(title)
215
+ end
216
+
217
+ self[:title] = title
218
+ end
219
+
220
+ # Gets the time this post was last updated. If _format_ is provided, the
221
+ # time will be returned as a formatted String. See Time.strftime for
222
+ # details.
223
+ def updated_at(format = nil)
224
+ if new?
225
+ format ? Time.now.strftime(format) : Time.now
226
+ else
227
+ format ? self[:updated_at].strftime(format) : self[:updated_at]
228
+ end
229
+ end
230
+
231
+ # Gets the URL for this post.
232
+ def url
233
+ Config.site['url'].chomp('/') + PostController.r(:/, name).to_s
234
+ end
235
+
236
+ def validate
237
+ validates_presence(:name, :message => 'Please enter a name for this post.')
238
+ validates_presence(:title, :message => 'Please enter a title for this post.')
239
+ validates_presence(:body, :message => "What's the matter? Cat got your tongue?")
240
+
241
+ validates_max_length(255, :title, :message => 'Please enter a title under 255 characters.')
242
+ validates_max_length(64, :name, :message => 'Please enter a name under 64 characters.')
243
+
244
+ validates_format(/^[0-9a-z_-]+$/i, :name, :message => 'Post names may only contain letters, numbers, underscores, and dashes.')
245
+ validates_format(/[a-z_-]/i, :name, :message => 'Post names must contain at least one non-numeric character.')
246
+ end
247
+
248
+ end
249
+
250
+ unless Post.count > 0
251
+ Post.create(
252
+ :title => 'Welcome to your new Thoth blog',
253
+ :body => %[
254
+ If you're reading this, you've successfully installed Thoth. Congratulations!
255
+
256
+ Once you've <a href="txmt://open/?url=file://#{Thoth.trait[:config_file]}">edited the config file</a> to your liking, you can <a href="/admin">login</a> and begin creating blog posts and pages. You can also delete this post to make way for your own glorious words.
257
+
258
+ Enjoy!
259
+ ].unindent
260
+ )
261
+ end
262
+ end
@@ -0,0 +1,80 @@
1
+ #--
2
+ # Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ module Thoth
30
+ class Tag < Sequel::Model
31
+ plugin :validation_helpers
32
+
33
+ one_to_many :tags_posts_map, :class => 'Thoth::TagsPostsMap'
34
+ many_to_many :posts, :class => 'Thoth::Post',
35
+ :join_table => :tags_posts_map, :read_only => true
36
+
37
+ #--
38
+ # Class Methods
39
+ #++
40
+
41
+ # Gets an array of tag names and post counts for tags with names that begin
42
+ # with the specified query string.
43
+ def self.suggest(query, limit = 1000)
44
+ tags = []
45
+
46
+ self.dataset.grep(:name, "#{query}%").all do |tag|
47
+ tags << [tag.name, tag.posts.count]
48
+ end
49
+
50
+ tags.sort!{|a, b| b[1] <=> a[1]}
51
+ tags[0, limit]
52
+ end
53
+
54
+ #--
55
+ # Instance Methods
56
+ #++
57
+
58
+ # Gets the Atom feed URL for this tag.
59
+ def atom_url
60
+ Config.site['url'].chomp('/') + TagController.r(:atom, name).to_s
61
+ end
62
+
63
+ # Gets published posts with this tag.
64
+ def posts
65
+ @posts ||= posts_dataset.filter(:is_draft => false).reverse_order(
66
+ :created_at)
67
+ end
68
+
69
+ # URL for this tag.
70
+ def url
71
+ Config.site['url'].chomp('/') + TagController.r(:/, name).to_s
72
+ end
73
+
74
+ def validate
75
+ validates_presence(:name)
76
+ validates_max_length(64, :name)
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ #--
2
+ # Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ module Thoth
30
+ class TagsPostsMap < Sequel::Model(:tags_posts_map)
31
+ many_to_one :tag, :class => 'Thoth::Tag'
32
+ many_to_one :post, :class => 'Thoth::Post'
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ #--
2
+ # Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ module Sequel; class Model
30
+
31
+ class Errors
32
+ def on_field(att)
33
+ on(att) || []
34
+ end
35
+ end
36
+
37
+ end; end
@@ -0,0 +1,105 @@
1
+ #--
2
+ # Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ require 'cgi'
30
+ require 'json'
31
+ require 'open-uri'
32
+ require 'timeout'
33
+
34
+ module Thoth; module Plugin
35
+
36
+ # Del.icio.us plugin for Thoth.
37
+ module Delicious
38
+ FEED_URL = 'http://feeds.delicious.com/feeds/json'
39
+
40
+ Config << {
41
+ 'delicious' => {
42
+
43
+ # Time in seconds to cache results. It's a good idea to keep this nice
44
+ # and high both to improve the performance of your site and to avoid
45
+ # pounding on del.icio.us's servers. Default is 900 seconds (15
46
+ # minutes).
47
+ 'cache_ttl' => 900,
48
+
49
+ # Request timeout in seconds.
50
+ 'request_timeout' => 5
51
+
52
+ }
53
+ }
54
+
55
+ class << self
56
+
57
+ # Gets recent del.icio.us bookmarks for the specified _username_. The
58
+ # return value of this method is cached to improve performance and to
59
+ # avoid pounding del.icio.us with excess traffic.
60
+ #
61
+ # Available options:
62
+ # [<tt>:count</tt>] Number of bookmarks to return (default is 5)
63
+ # [<tt>:tags</tt>] Array of tags to filter by. Only bookmarks with the
64
+ # specified tags will be returned.
65
+ #
66
+ def recent_bookmarks(username, options = {})
67
+ cache = Ramaze::Cache.plugin
68
+ options = {:count => 5}.merge(options)
69
+ request = "#{FEED_URL}/#{::CGI.escape(username)}" <<
70
+ (options[:tags] ? '/' << ::CGI.escape(options[:tags].join(' ')) : '') <<
71
+ "?raw&count=#{options[:count]}"
72
+
73
+ if value = cache[request]
74
+ return value
75
+ end
76
+
77
+ response = []
78
+
79
+ Timeout.timeout(Config.delicious['request_timeout'], StandardError) do
80
+ response = JSON.parse(open(request).read)
81
+ end
82
+
83
+ # Parse the response into a more friendly format.
84
+ data = []
85
+
86
+ response.each do |item|
87
+ data << {
88
+ :url => item['u'],
89
+ :desc => item['d'],
90
+ :note => item['n'] ? item['n'] : '',
91
+ :tags => item['t'] ? item['t'] : []
92
+ }
93
+ end
94
+
95
+ return cache.store(request, data, :ttl => Config.delicious['cache_ttl'])
96
+
97
+ rescue => e
98
+ Ramaze::Log.error "Thoth::Plugin::Delicious: #{e.message}"
99
+ return []
100
+ end
101
+
102
+ end
103
+ end
104
+
105
+ end; end
@@ -0,0 +1,86 @@
1
+ #--
2
+ # Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of this project nor the names of its contributors may be
14
+ # used to endorse or promote products derived from this software without
15
+ # specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+ require 'timeout'
30
+ require 'net/flickr'
31
+
32
+ module Thoth; module Plugin
33
+
34
+ # Flickr plugin for Thoth.
35
+ module Flickr
36
+
37
+ Config << {
38
+ 'flickr' => {
39
+
40
+ # Flickr API key. You can either use the default or replace this with
41
+ # your own key.
42
+ 'api_key' => '5b1d9919cb2d97585bd3d83e05af80b8',
43
+
44
+ # Time in seconds to cache results. It's a good idea to keep this nice
45
+ # and high both to improve the performance of your site and to avoid
46
+ # pounding on Flickr's servers. Default is 900 seconds (15 minutes).
47
+ 'cache_ttl' => 900,
48
+
49
+ # Request timeout in seconds.
50
+ 'request_timeout' => 5
51
+
52
+ }
53
+ }
54
+
55
+ class << self
56
+
57
+ # Gets recent Flickr photos (up to _limit_) for the specified _username_.
58
+ # The return value of this method is cached to improve performance and to
59
+ # avoid abusing the Flickr API.
60
+ def recent_photos(username, limit = 4)
61
+ cache = Ramaze::Cache.plugin
62
+ key = "recent_photos_#{username}_#{limit}"
63
+
64
+ if value = cache[key]
65
+ return value
66
+ end
67
+
68
+ @flickr ||= Net::Flickr.new(Config.flickr['api_key'])
69
+
70
+ begin
71
+ Timeout.timeout(Config.flickr['request_timeout'].to_i, StandardError) do
72
+ value = cache.store(key, @flickr.people.find_by_username(username).
73
+ photos(:per_page => limit), :ttl => Config.flickr['cache_ttl'])
74
+ end
75
+ rescue => e
76
+ Ramaze::Log.error "Thoth::Plugin::Flickr: #{e.message}"
77
+ return []
78
+ else
79
+ value
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+
86
+ end; end