Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 460
- Log:
Changeset #459 broke ungrouped listings, as the ungrouped item internally
is a Symbol not String and "gsub" is a private method for that class.
- Author:
- rool
- Date:
- Tue Dec 17 12:21:39 +0000 2013
- Size:
- 14141 Bytes
1 | # See "dir_list_tags_extension.rb" for details. |
2 | |
3 | module DirListTags |
4 | include Radiant::Taggable |
5 | |
6 | desc %{ |
7 | Executes a Perl script at the given path and includes its <code>stdout</code> result in the page. Output whatever type of text is used by that page, or if wanting to output HTML into a page with a non-HTML filter, wrap the tag in <code><notextile>...</notextile></code> or equivalent for the filter in use. |
8 | |
9 | If the path you require includes spaces or other special characters, escape them with a backslash. |
10 | |
11 | <pre><code><r:perl_script_output location="/home/foo/perl_script.pl" /></code></pre> |
12 | } |
13 | tag "perl_script_output" do |tag| |
14 | unless location = tag.attr['location'] |
15 | raise TagError.new("`perl_script_output' tag must contain `location' attribute") |
16 | end |
17 | |
18 | command = "`perl #{location}`" |
19 | result = eval(command) |
20 | |
21 | if ($?.success?) |
22 | result |
23 | else |
24 | raise TagError.new("Perl command failed with exit status #{$?.exitstatus}") |
25 | end |
26 | end |
27 | |
28 | desc %{ |
29 | Enumerates the contents of a directory within <code><li>...</li></code> containers. Environment variable SERVER_DOCUMENT_ROOT must be set and is used as the root path for directory enumeration. Even so, directory traversal is not prevented so only use this tag when content editors are trusted. |
30 | |
31 | You must wrap output in <code><notextile>...</notextile></code> or an equivalent if you're using a filtered page type since the tag's value is raw HTML. An outer container such as <code><ul></code> or <code><ol></code> must be placed around the tag as only the inner list items are returned. |
32 | |
33 | <pre><code><r:linked_directory_listing dir="search_directory" /></code></pre> |
34 | |
35 | Certain magic directories are ignored (CVS, .svn). |
36 | } |
37 | tag "linked_directory_listing" do |tag| |
38 | unless dir = tag.attr['dir'] |
39 | raise TagError.new("`linked_directory_listing' tag must contain `dir' attribute") |
40 | end |
41 | |
42 | docroot = ENV['SERVER_DOCUMENT_ROOT'] |
43 | # See description text in "dir_list_tags_extension.rb" for details. |
44 | raise TagError.new("You must make environment variable SERVER_DOCUMENT_ROOT available to Radiant") if (docroot.nil?) |
45 | |
46 | "<notextile>" + recursive_directory_list_in_li_tags(docroot, dir) + "</notextile>" |
47 | end |
48 | |
49 | desc %{ |
50 | Renders a set of HTML tables that enumerate a directory in a parsed, augmented fashion. Environment variable SERVER_DOCUMENT_ROOT must be set and is used as the root path for directory enumeration. Even so, directory traversal is not prevented so only use this tag when content editors are trusted. |
51 | |
52 | Output is wrapped in <code><notextile>...</notextile></code> on assumption of Textile filtering. |
53 | |
54 | Certain magic directories are ignored (CVS, .svn). The directory can contain a file 'config.yml' in subdirectory 'config', which works as described below. The intention is that each table corresponds to a group of files in some kind of file drop box, with a header above naming the group. |
55 | |
56 | <pre><code><r:linked_parsed_directory_listing dir="search_directory" |
57 | link_base="foo" |
58 | link_icon="bar" /></code></pre> |
59 | |
60 | *Drop box display configuration* |
61 | |
62 | Filenames of entries in the drop box are converted by looking for everything up to the first '.' and treating this as a leafname. The leafname is looked up in the configuration hash. After the '.', anything up to but not including a filename extension is treated as a version string. Filename extensions are assumed to be present and form a three-letter code after a final '.'. For 'tar.gz', use 'tgz'. |
63 | |
64 | Each entry in the tables of items can have a 'Details' column. These contain links, formed by appending the leafname as described above to whatever value is provided in the Radiant tag's "link_base" attribute. If the attribute is omitted, the column is omitted too. If included, the links contain the text "Details". To use an icon instead, provide a path to the icon in the "link_icon" attribute. |
65 | |
66 | Rails helper methods are used to create human-readable versions of default strings from the path components. For examples, please see <a href="http://rails.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html" target="_blank">this part of the Rails API</a>. |
67 | |
68 | The configuration file is optional and allows the writer to both override strings generated with the above method, as well as specify things which cannot be determined purely from the drop box filenames. Syntax are as follows: |
69 | |
70 | <pre><code> appname_1: |
71 | config_item_1: config_value |
72 | config_item_2: config_value |
73 | config_item_3: config_value |
74 | |
75 | appname_2: |
76 | config_item_1: config_value |
77 | config_item_2: config_value |
78 | config_item_3: config_value</code></pre> |
79 | |
80 | ...and so-on, i.e. it's a very simple usage of the YAML syntax. |
81 | |
82 | *Items which override values determined from the filename* |
83 | |
84 | <pre><code> name: 'Component display name" |
85 | version: 'Version string' |
86 | icon: 'Icon filename name' (no path components allowed) |
87 | link: 'Details link' (Wiki by default; for links within the |
88 | ROOL site, use "/foo/bar/baz.html" - |
89 | i.e. do NOT include the host name)</code></pre> |
90 | |
91 | *Items which are optional* |
92 | |
93 | <pre><code> info: 'One-liner description' (else "-" by default) |
94 | group: 'Group name' (groups are sorted alphabetically and |
95 | ungrouped items are listed afterwards; |
96 | use singular non-abbreviated forms)</code></pre> |
97 | |
98 | *Icons and overall layout* |
99 | |
100 | Icons must be placed in subdirectory @icons@, again within the drop box, so that it forms a tree as follows: |
101 | |
102 | <pre><code> dropbox_root |
103 | | |
104 | +--config |
105 | | | |
106 | | +--config.yml |
107 | | |
108 | +--icons |
109 | | | |
110 | | +--app1.png |
111 | | +--app2.png |
112 | | +--etc... |
113 | | |
114 | +--app1.vsnstring.zip |
115 | +--app2.vsnstring.zip |
116 | +--etc...</code></pre> |
117 | |
118 | If an icon cannot be found, @icons/_default.png@ is tried. If that doesn't exist either, the relevant table cell will be left blank. |
119 | } |
120 | tag "linked_parsed_directory_listing" do |tag| |
121 | unless dir = tag.attr['dir'] |
122 | raise TagError.new("`linked_parsed_directory_listing' tag must contain `dir' attribute") |
123 | end |
124 | |
125 | link_base = tag.attr['link_base'] |
126 | link_icon = tag.attr['link_icon'] |
127 | |
128 | docroot = ENV['SERVER_DOCUMENT_ROOT'] |
129 | # See description text in "dir_list_tags_extension.rb" for details. |
130 | raise "You must make environment variable SERVER_DOCUMENT_ROOT available to Radiant" if (docroot.nil?) |
131 | |
132 | "<notextile>" + parsed_directory_list_in_table(docroot, dir, link_base, link_icon) + "</notextile>" |
133 | end |
134 | |
135 | # Support the various directory listing tags. Returns an array of items |
136 | # describing a directory contents and the contents of any subdirectories |
137 | # as a flat unsorted list. The last parameter is 'false' to avoid scanning |
138 | # to a level beyond the current directory. |
139 | # |
140 | require 'find' |
141 | # |
142 | def recursive_directory_list(base, dir, recurse = true) |
143 | # Partly based on: |
144 | # |
145 | # http://www.oreillynet.com/onjava/blog/2006/03/recursive_directory_list_with.html |
146 | |
147 | excludes = [ 'CVS', '.svn' ] |
148 | collect = []; |
149 | dir = File.join( base, dir ) |
150 | first = true |
151 | |
152 | Find.find(dir) do |path| |
153 | if FileTest.directory?(path) |
154 | if (recurse == false) |
155 | if (first) |
156 | first = false |
157 | next |
158 | else |
159 | Find.prune |
160 | end |
161 | else |
162 | if (excludes.include?(File.basename(path))) |
163 | Find.prune # Don't look any further into this directory. |
164 | else |
165 | next |
166 | end |
167 | end |
168 | else |
169 | mod = File.mtime(path) |
170 | collect.push({ |
171 | :leaf => File.basename(path), |
172 | :mod => File.mtime(path), |
173 | :link => "#{path[base.length..-1]}?#{mod.tv_sec}", |
174 | :size => number_to_human_size(File.size(path)) |
175 | }); |
176 | end |
177 | end |
178 | |
179 | return collect |
180 | end |
181 | |
182 | # Support the linked_directory_listing tag. |
183 | # |
184 | def recursive_directory_list_in_li_tags(base, dir) |
185 | html = '' |
186 | |
187 | recursive_directory_list(base, dir).sort do |x, y| |
188 | #x[:leaf] <=> y[:leaf] |
189 | y[:mod] <=> x[:mod] |
190 | end.each do |entry| |
191 | html << "<li><a href=\"#{entry[:link]}\"><b>#{entry[:leaf]}</b></a> (#{entry[:size]})<br /><small>Last modified #{entry[:mod]}</small></li>" |
192 | end |
193 | |
194 | html = '<li>There are no files currently available.</li>' if html.empty? |
195 | return html |
196 | end |
197 | |
198 | # Ripped straight out of ActionView::Helpers::NumberHelper. |
199 | # |
200 | def number_to_human_size(size) |
201 | case |
202 | when size < 1.kilobyte: '%d Bytes' % size |
203 | when size < 1.megabyte: '%.1f KB' % (size / 1.0.kilobyte) |
204 | when size < 1.gigabyte: '%.1f MB' % (size / 1.0.megabyte) |
205 | when size < 1.terabyte: '%.1f GB' % (size / 1.0.gigabyte) |
206 | else '%.1f TB' % (size / 1.0.terabyte) |
207 | end.sub('.0', '') |
208 | rescue |
209 | nil |
210 | end |
211 | |
212 | # Support the linked_parsed_directory_listing tag. |
213 | # |
214 | def parsed_directory_list_in_table( base, dir, link_base, link_icon ) |
215 | |
216 | list = recursive_directory_list(base, dir, false) |
217 | |
218 | # Load the configuration file, if provided |
219 | |
220 | begin |
221 | configuration = YAML.load_file( File.join( base, dir, 'config', 'config.yml' ) ) |
222 | rescue |
223 | configuration = {} |
224 | end |
225 | |
226 | # Assemble the configured strings within groups |
227 | |
228 | groups = {} |
229 | |
230 | list.each do |entry| |
231 | |
232 | # Parser: Various charaters, a dot, then: one or more digits (0-9) |
233 | # followed by an optional dot, repeated at least once, this whole |
234 | # assembly optional, recording only the collection of digits and |
235 | # dots, not individual digits-plus-dots sets ("(?:" => don't include |
236 | # this group in the match data). Then zero or more other characters, |
237 | # non-greedy. |
238 | |
239 | leaf = entry[:leaf] |
240 | regexp = /^(.*?)\.((?:[0-9]+\.?)+)?(.*?)$/ |
241 | scanned = leaf.scan( regexp )[ 0 ] |
242 | base_name = scanned[0] |
243 | version = (scanned[1] || '').chomp('.') # May have trailing '.' |
244 | filetype = scanned[2] || '' |
245 | |
246 | config = configuration[base_name] || configuration[leaf] || {} |
247 | |
248 | name = config['name'] || base_name.humanize.titleize |
249 | version = config['version'] || ((version.empty?) ? '-' : version) |
250 | icon = config['icon'] || "#{base_name}.png" |
251 | link = config['link'] || "#{link_base}#{name}" |
252 | info = config['info'] || '-' |
253 | group = config['group'] || :Ungrouped |
254 | md5 = config['md5'] |
255 | md5_time = config['md5_time'] |
256 | |
257 | unless ( md5.nil? || md5_time.nil? ) |
258 | md5, md5_time = nil if ( entry[:mod] > md5_time ) |
259 | end |
260 | |
261 | icon = "#{dir}/icons/#{icon}" |
262 | |
263 | unless (File.exist?("#{base}#{icon}")) |
264 | icon = "#{dir}/icons/_default.png" |
265 | unless (File.exist?("#{base}#{icon}")) |
266 | icon = '' |
267 | end |
268 | end |
269 | |
270 | # Change "foo/bar/baz" to "foo / bar / baz" - i.e. a "/" without a |
271 | # space either side is changed to a slash with a space on each side. |
272 | # Helps enormously with line wrapping in browsers. |
273 | |
274 | info.gsub!( /([^< ])\/([^ ])/, '\1 / \2' ) |
275 | |
276 | groups[ group ] = [] if (groups[ group ].nil?) |
277 | groups[ group ].push( { |
278 | :name => name, |
279 | :version => version, |
280 | :icon => icon, |
281 | :link => link, |
282 | :info => info, |
283 | :md5 => md5, |
284 | :raw => entry |
285 | } ) |
286 | end |
287 | |
288 | html = '' |
289 | |
290 | groups.keys.sort do | x, y | |
291 | |
292 | # Sort the groups, pushing keys of Symbol class (i.e. ':Ungrouped') |
293 | # to the end. |
294 | |
295 | if ( x.class == Symbol ) |
296 | 1 |
297 | elsif( y.class == Symbol ) |
298 | -1 |
299 | else |
300 | x <=> y |
301 | end |
302 | |
303 | end.each do | group_key | |
304 | |
305 | # For each sorted key, output a table. |
306 | |
307 | dscwd = link_base ? '40%' : '50%' |
308 | |
309 | count = 0 |
310 | html << "<h3>#{group_key}<a name=\"#{group_key.to_s.gsub(/\W/, '_').downcase}\" style=\"text-decoration: none; border-bottom: none; font-size: 1px\"> </a></h3>\n" |
311 | html << "<table width=\"100%\" class=\"parsed_directory_listing\" border=\"0\">\n" |
312 | html << "<tr><th width=\"10%\">Icon</th><th width=\"20%\" align=\"left\">Name, date & MD5</th><th width=\"#{dscwd}\" align=\"left\">Description</th><th width=\"10%\">Version</th><th width=\"10%\">Size</th>" |
313 | html << "<th width=\"10%\">Details</th>" if (link_base) |
314 | html << "</tr>\n" |
315 | |
316 | # Sort the items inside each group by name and output each in a table |
317 | # row. |
318 | |
319 | groups[group_key].sort do | x, y | |
320 | |
321 | x[:name] <=> y[:name] |
322 | |
323 | end.each do |entry| |
324 | |
325 | row_class = (count % 2 == 0) ? 'even' : 'odd' |
326 | count += 1 |
327 | |
328 | html << "<tr class=\"#{row_class}\">" |
329 | |
330 | if (entry[:icon].empty?) |
331 | html << "<td> </td>" |
332 | else |
333 | html << "<td align=\"center\"><a href=\"#{entry[:raw][:link]}\" class=\"img\"><img src=\"#{entry[:icon]}\" alt=\"icon\" /></a></td>" |
334 | end |
335 | |
336 | time = entry[:raw][:mod] |
337 | |
338 | if (time.nil?) |
339 | tstr = '' |
340 | else |
341 | tstr = "<div class=\"parsed_directory_listing_datestamp\">" << |
342 | "#{time.strftime('%Y-%m-%d')} " << |
343 | "#{time.strftime('%H:%M:%S')}" << |
344 | "</div>" |
345 | end |
346 | |
347 | md5 = entry[:md5] |
348 | |
349 | if (md5.nil?) |
350 | md5str = '' |
351 | else |
352 | md5str = "<div class=\"parsed_directory_listing_md5\">#{ md5 }</div>" |
353 | end |
354 | |
355 | html << "<td><a name=\"#{entry[:name].gsub(/\W/, '_').downcase}\" href=\"#{entry[:raw][:link]}\">#{entry[:name]}</a>#{tstr}#{md5str}</td>" |
356 | html << "<td class=\"can_wrap\">#{entry[:info]}</td>" |
357 | html << "<td align=\"center\">#{entry[:version]}</td>" |
358 | html << "<td align=\"center\">#{entry[:raw][:size]}</td>" |
359 | |
360 | if (link_base) |
361 | if (link_icon) |
362 | html << "<td align=\"center\"><a href=\"#{entry[:link]}\" class=\"img\"><img src=\"#{link_icon}\" alt=\"info\" /></a></td>" |
363 | else |
364 | html << "<td align=\"center\"><a href=\"#{entry[:link]}\">Details</a></td>" |
365 | end |
366 | end |
367 | |
368 | html << "</tr>\n" |
369 | end |
370 | |
371 | html << "</table>\n\n" |
372 | end |
373 | |
374 | html = '<p>There are no files currently available.</p>' if (list.empty?) |
375 | return html |
376 | end |
377 | |
378 | end |