rethoth 0.4.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 (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