Graphiclious 0.1.0

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 (50) hide show
  1. data/README.txt +126 -0
  2. data/Rakefile +28 -0
  3. data/bin/graphiclious +3 -0
  4. data/doc/cloud.jpg +0 -0
  5. data/doc/index.html +32 -0
  6. data/doc/screenshots/default.css +44 -0
  7. data/doc/screenshots/original/blue-style-1.html +37 -0
  8. data/doc/screenshots/original/blue-style-1.jpg +0 -0
  9. data/doc/screenshots/original/cornflower.html +37 -0
  10. data/doc/screenshots/original/cornflower.jpg +0 -0
  11. data/doc/screenshots/original/custom-style.html +37 -0
  12. data/doc/screenshots/original/custom-style.jpg +0 -0
  13. data/doc/screenshots/original/sample-graph.html +37 -0
  14. data/doc/screenshots/original/sample-graph.jpg +0 -0
  15. data/doc/screenshots/original/user-interface.html +37 -0
  16. data/doc/screenshots/original/user-interface.jpg +0 -0
  17. data/doc/screenshots/thumb.html +46 -0
  18. data/doc/screenshots/thumb/t_blue-style-1.jpg +0 -0
  19. data/doc/screenshots/thumb/t_cornflower.jpg +0 -0
  20. data/doc/screenshots/thumb/t_custom-style.jpg +0 -0
  21. data/doc/screenshots/thumb/t_sample-graph.jpg +0 -0
  22. data/doc/screenshots/thumb/t_user-interface.jpg +0 -0
  23. data/doc/style.css +60 -0
  24. data/gemspec.rb +45 -0
  25. data/lib/graphiclious.rb +78 -0
  26. data/lib/graphiclious/delicious2yaml.rb +235 -0
  27. data/lib/graphiclious/dot_attributes.rb +35 -0
  28. data/lib/graphiclious/environment.rb +36 -0
  29. data/lib/graphiclious/gui.rb +260 -0
  30. data/lib/graphiclious/help.rb +22 -0
  31. data/lib/graphiclious/require_fox.rb +12 -0
  32. data/lib/graphiclious/url_graph.rb +135 -0
  33. data/lib/graphiclious/utf8.rb +19 -0
  34. data/lib/graphiclious/version.rb +8 -0
  35. data/lib/graphiclious/yaml-links2html.rb +476 -0
  36. data/options.yaml +7 -0
  37. data/styles/blue-style-1/dot-attributes.yaml +21 -0
  38. data/styles/blue-style-1/extern.gif +0 -0
  39. data/styles/blue-style-1/style.css +128 -0
  40. data/styles/cornflower/cornflower.png +0 -0
  41. data/styles/cornflower/dot-attributes.yaml +21 -0
  42. data/styles/cornflower/extern.gif +0 -0
  43. data/styles/cornflower/h3.gif +0 -0
  44. data/styles/cornflower/menuitem.png +0 -0
  45. data/styles/cornflower/menuselected.png +0 -0
  46. data/styles/cornflower/style.css +149 -0
  47. data/styles/cornflower/tr-edge.png +0 -0
  48. data/test/test_delicious2yaml.rb +97 -0
  49. data/wickie +249 -0
  50. metadata +119 -0
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ #$Id: utf8.rb,v 1.2 2005/05/05 13:37:59 wsdng Exp $
3
+
4
+ module Graphiclious
5
+ def self.utf8_as_string(aString)
6
+ return aString.unpack("U*").pack("C*")
7
+ end
8
+
9
+ def self.string_as_utf8(aString)
10
+ aString.unpack("C*").pack("U*")
11
+ end
12
+
13
+ # Convert link as provided by del.icio.us via rubilicious
14
+ # to link containing utf8 strings for description and extended
15
+ # information
16
+ def self.link_with_utf8_strings(link)
17
+ link
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/ruby
2
+ #$Id: version.rb,v 1.1 2005/04/03 14:45:26 wsdng Exp $
3
+
4
+ module Graphiclious
5
+ def self.version
6
+ '0.1.0'
7
+ end
8
+ end
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env ruby
2
+ #$Id: yaml-links2html.rb,v 1.20 2006/03/09 22:57:52 wsdng Exp $
3
+
4
+ require 'yaml'
5
+ require 'ftools'
6
+ require 'graphiclious/environment'
7
+ require 'graphiclious/url_graph'
8
+
9
+ class YamlLinksToHtml
10
+ DEL_ICIO_US_URL = "http://del.icio.us"
11
+ STYLES_FOLDER = File.join(GRAPHICLIOUS_PATH, "styles")
12
+ MAX_STYLES_IN_CLOUD = 10
13
+ CLOUD_STYLES = (1..MAX_STYLES_IN_CLOUD).collect{ |size| 'cloud-' + size.to_s }
14
+
15
+ attr_accessor(:link_to_delicious_if_possible, :use_bundles, :include_graph_views, :build_graph_thumbs, :include_private)
16
+
17
+ # Lazy require things which some users won't use
18
+ def check_prerequisites
19
+ require 'RMagick' if @build_graph_thumbs
20
+ end
21
+
22
+ def self.guess_styles
23
+ guess_styles_within(STYLES_FOLDER)
24
+ end
25
+
26
+ def self.guess_styles_within(folder)
27
+ Dir[File.join(folder, '*', 'dot-attributes.yaml')].collect {
28
+ |filename|
29
+ File.dirname(filename)
30
+ }
31
+ end
32
+
33
+ def self.guess_users_within(folder)
34
+ Dir[File.join(folder, '*', 'delicious.yaml')].collect {
35
+ |filename|
36
+ File.dirname(filename)
37
+ }
38
+ end
39
+
40
+ def set_working_dir(new_working_dir)
41
+ @working_dir = new_working_dir
42
+ set_user(@user)
43
+ end
44
+
45
+ def set_user(user)
46
+ @user = user
47
+ @input_file = File.join(@working_dir, @user, 'delicious.yaml')
48
+ @html_output_path = File.join(@working_dir, @user, 'html')
49
+ @bundles_file = File.join(@working_dir, @user, 'bundles.yaml')
50
+ end
51
+
52
+ def set_style style
53
+ @style = style
54
+ @dot_attribut_file = "#{@style}/dot-attributes.yaml"
55
+ end
56
+
57
+ def initialize
58
+ @include_private = false
59
+ @working_dir = Dir.getwd
60
+ set_style(YamlLinksToHtml.guess_styles[0])
61
+ set_user(YamlLinksToHtml.guess_users_within(@working_dir)[0])
62
+ @link_to_delicious_if_possible = false
63
+ @use_bundles = false
64
+ @include_graph_views = false
65
+ @build_graph_thumbs = @include_graph_views
66
+ end
67
+
68
+ def copy_style_to_user
69
+ ['dot-attributes.yaml', '*.jpg', '*.png', '*.gif', '*.css'].each do
70
+ |pattern|
71
+ Dir[File.join(@style, pattern)].each do
72
+ |template_file|
73
+ write_line_on_protokol("Copy #{template_file} to #{@user}")
74
+ File.copy(template_file, File.join(@working_dir, @user))
75
+ end
76
+ end
77
+ end
78
+
79
+ def set_protocol_block(aProc)
80
+ @protocol_block = aProc
81
+ end
82
+
83
+ def write_line_on_protokol(lineString)
84
+ puts lineString
85
+ unless @protocol_block.nil?
86
+ @protocol_block.call(lineString)
87
+ end
88
+ end
89
+
90
+ # methods for html-generation
91
+
92
+ def os_file_join(dir, file)
93
+ if RUBY_PLATFORM =~ /win32/
94
+ File.join(dir, file).gsub(File::SEPARATOR, "\\")
95
+ else
96
+ File.join(dir, file)
97
+ end
98
+ end
99
+
100
+ def downscale(val)
101
+ (2 * Math.log(val)).round
102
+ end
103
+
104
+ def get_cloud_style(val, maxval)
105
+ quot = downscale(maxval)
106
+ quot = 1 if quot == 0
107
+ index = (MAX_STYLES_IN_CLOUD - 1) * downscale(val) / quot
108
+ if(index >= MAX_STYLES_IN_CLOUD)
109
+ puts "Corrected index of cloud style for #{val} from #{index} to #{MAX_STYLES_IN_CLOUD}"
110
+ index = MAX_STYLES_IN_CLOUD
111
+ end
112
+ CLOUD_STYLES[index]
113
+ end
114
+
115
+ def delicious_tag_url(tag1, *other_tags)
116
+ url = "#{DEL_ICIO_US_URL}/tag/#{tag1}"
117
+ other_tags.each { |tag| url = "#{url}+#{tag}" }
118
+ url + '?settagview=cloud'
119
+ end
120
+
121
+ def delicious_user_url(tag1, *other_tags)
122
+ url = "#{DEL_ICIO_US_URL}/#{@user}/#{tag1}"
123
+ other_tags.each { |tag| url = "#{url}+#{tag}" }
124
+ url + '?settagview=cloud'
125
+ end
126
+
127
+ def write_html_head_on(fp)
128
+ fp.puts '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'
129
+ fp.puts '<html><head>'
130
+ fp.puts '<meta http-equiv="content-type" content="text/html; charset=UTF-8">'
131
+ fp.puts '<link rel="stylesheet" type="text/css" href="../style.css">'
132
+ fp.puts '</head><body>'
133
+ end
134
+
135
+ def write_html_tail_on(fp)
136
+ fp.puts '</body></html>'
137
+ end
138
+
139
+ def menuitem(name, id, url)
140
+ if(name == id)
141
+ "<div class=\"menuselected\">#{name}</div>"
142
+ else
143
+ "<div class=\"menuitem\"><a href=\"#{url}\">#{name}</a></div>"
144
+ end
145
+ end
146
+
147
+ def link_index_on(fp, id='none', bundles={})
148
+ fp.puts '<div class="mainmenu">'|| @checkBundles.checked?
149
+ fp.puts menuitem('cloud (all)', id, 'index.htm')
150
+ unless bundles.empty?
151
+ bundles.keys.sort.each do
152
+ |bundle|
153
+ unless bundle == 'unbundled'
154
+ fp.puts menuitem(bundle, id, "bundle_#{bundle}.htm")
155
+ end
156
+ end
157
+ unless bundles['unbundled'].empty?
158
+ fp.puts menuitem('unbundled', id, "bundle_unbundled.htm")
159
+ end
160
+ end
161
+ fp.puts '</div>'
162
+ end
163
+
164
+ def write_overview(filename, cloud_name, list, bundles, links_by_tag)
165
+ valid_list = list.reject { |tag|
166
+ links_by_tag[tag].nil? # doesn't make sense but seems to be possible
167
+ }
168
+ File.open(File.join(@html_output_path, filename), 'w') do
169
+ | fp |
170
+ write_html_head_on(fp)
171
+ fp.puts "<h1>Tag overview: #{cloud_name}</h1>"
172
+ link_index_on(fp, cloud_name, bundles)
173
+ fp.puts '<div class="main">'
174
+ fp.puts "<h3>Tags, size representing number of links</h3>"
175
+ max_num_links = valid_list.collect{ |tag| links_by_tag[tag].size }.max
176
+ valid_list.each do
177
+ |tag|
178
+ style = get_cloud_style(links_by_tag[tag].size, max_num_links)
179
+ fp.puts(UrlGraph.node_ankor(tag, style))
180
+ end
181
+ if(@include_graph_views)
182
+ name = cloud_name_to_graph_name(cloud_name)
183
+ fp.puts "<h3>Graph of tag relations</h3>"
184
+
185
+ link_desc = if(@build_graph_thumbs)
186
+ "<img src=\"#{convert_to_thumbnail_filename(name + '.gif')}\">"
187
+ else
188
+ 'view it'
189
+ end
190
+ fp.puts "<a href=\"#{name}.htm\">#{link_desc}</a>"
191
+ fp.puts '</div>'
192
+ end
193
+ end
194
+ end
195
+
196
+ def sorted_links(links)
197
+ links.sort { |l1, l2| l1['description'] <=> l2['description'] }
198
+ end
199
+
200
+ def write_link_on(link, fp, skip_tags = [])
201
+ fp.puts(UrlGraph.ankor(link['href'], link['description']))
202
+ info = link['extended']
203
+ if info
204
+ fp.puts('<br>')
205
+ fp.puts("#{info}")
206
+ end
207
+ other_tags = link['tags'].reject { |t| skip_tags.include?(t) }
208
+ unless other_tags.empty?
209
+ fp.puts('<br>')
210
+ fp.puts('More tags: ')
211
+ other_tags.each { |t|
212
+ fp.puts(UrlGraph.node_ankor(t))
213
+ }
214
+ end
215
+ end
216
+
217
+ # create an empty graph with dot attributes of chosen style
218
+ def init_tag_graph
219
+ @tag_graph = UrlGraph.new
220
+ if File.exists?(@dot_attribut_file)
221
+ @tag_graph.fixed_dot_attributes = File.open(@dot_attribut_file, 'r') { |io|
222
+ YAML.load(io)
223
+ }
224
+ else
225
+ # write out the default-attribute-file to make
226
+ # things easy to configure
227
+ File.open(@dot_attribut_file, 'w') { |out|
228
+ YAML.dump(@tag_graph.fixed_dot_attributes, out)
229
+ }
230
+ write_line_on_protokol "Wrote #{@dot_attribut_file}, change to adopt different style."
231
+ end
232
+ end
233
+
234
+ def load_links
235
+ # load yaml-file containing links as a hash
236
+ cache = File.open(@input_file, 'r') { |io|
237
+ YAML.load(io)
238
+ }
239
+ links = cache.values
240
+ unless(@include_private)
241
+ links = links.reject { |l| l['shared'] =~ /no/ }
242
+ end
243
+ @links_by_tag = Hash.new
244
+ @links_by_edge = Hash.new
245
+ links.each do
246
+ |link|
247
+ tagsOfLink = link['tags']
248
+ n = tagsOfLink.size - 1
249
+ (0..n).each do
250
+ |i|
251
+ tag = tagsOfLink[i]
252
+ unless @links_by_tag.has_key?(tag)
253
+ @links_by_tag[tag] = Array.new
254
+ @tag_graph.add_vertex(tag)
255
+ end
256
+ @links_by_tag[tag].push(link)
257
+ (i+1..n).each do
258
+ |j|
259
+ otherTag = tagsOfLink[j]
260
+ # write_line_on_protokol "\t#{tag}<->#{otherTag}"
261
+ @tag_graph.add_edge(tag, otherTag)
262
+ edge_name = UrlGraph.edge_name(tag, otherTag)
263
+ unless @links_by_edge.has_key?(edge_name)
264
+ @links_by_edge[edge_name] = Array.new
265
+ end
266
+ @links_by_edge[edge_name].push(link)
267
+ end
268
+ end
269
+ end
270
+
271
+ # load bundles file
272
+ @bundles = {}
273
+ if @use_bundles
274
+ @bundles = File.open(@bundles_file, 'r') { |io|
275
+ YAML.load(io)
276
+ }
277
+ set_of_unbundled_tags = {}
278
+ @tag_graph.each { |tag| set_of_unbundled_tags[tag] = tag }
279
+ @bundles.keys.each { |bundle|
280
+ @bundles[bundle].each { |tag|
281
+ set_of_unbundled_tags.delete(tag)
282
+ }
283
+ }
284
+ @bundles['unbundled'] = set_of_unbundled_tags.values.sort
285
+ end
286
+ end
287
+
288
+ def fatal(msg)
289
+ write_line_on_protokol(msg)
290
+ exit(1)
291
+ end
292
+
293
+ def thumbnail(filename)
294
+ thumb_name = convert_to_thumbnail_filename(filename)
295
+ write_line_on_protokol("Writing thumbnail #{thumb_name} for #{filename}")
296
+ img = Magick::Image.read(filename)[0]
297
+ thumb = img.thumbnail(100, 100)
298
+ thumb.write(thumb_name)
299
+ end
300
+
301
+ # name.extension will result in name.thumb.extension
302
+ def convert_to_thumbnail_filename(filename)
303
+ parts = filename.split('.')
304
+ ext = parts.pop
305
+ parts[0..-1].push('thumb').push(ext).join('.')
306
+ end
307
+
308
+ def do_graphviz(tag_graph, cloud_name)
309
+ return unless @include_graph_views
310
+ name = cloud_name_to_graph_name(cloud_name)
311
+ # Write Graphviz-File
312
+ tmpDotFile = 'graph.dot~'
313
+ tag_graph.print_dotted_on_file(tmpDotFile)
314
+ write_line_on_protokol "Wrote #{tmpDotFile} for temporary use, feel free to delete later."
315
+
316
+ # Use Graphviz to create HTML with client-side imagemap
317
+ os_gif_file = os_file_join(@html_output_path, "#{name}.gif")
318
+ dot_command = "dot -Tgif #{tmpDotFile} -o #{os_gif_file}"
319
+ system(dot_command) || fatal("Error can't execute #{dot_command}\nPlease check your graphviz installation")
320
+ write_line_on_protokol "Wrote #{os_gif_file}."
321
+ graph_htm = File.join(@html_output_path, "#{name}.htm")
322
+ os_graph_htm = os_file_join(@html_output_path, "#{name}.htm")
323
+ File.open(graph_htm, 'w') { |fp|
324
+ write_html_head_on(fp)
325
+ fp.puts("<h1>Tag graph: #{cloud_name} </h1>")
326
+ link_index_on(fp, '', @bundles)
327
+ fp.puts '<div class="main">'
328
+ fp.puts '<IMG SRC="' + "#{name}.gif" + '" USEMAP=#UrlGraph>'
329
+ fp.puts '</div>'
330
+ }
331
+ dot_command = "dot -Tcmapx #{tmpDotFile} -o graph.htm~"
332
+ system(dot_command) || fatal("Failed system call: #{dot_command}")
333
+ File.open(graph_htm, 'a') { |fp|
334
+ File.open('graph.htm~'){ |fp2| fp2.each_line { |line| fp.puts(line) }}
335
+ write_html_tail_on(fp)
336
+ }
337
+ write_line_on_protokol "Wrote #{os_graph_htm} (which uses #{os_gif_file})."
338
+
339
+ return unless @build_graph_thumbs
340
+ gif_file = File.join(@html_output_path, "#{name}.gif")
341
+ thumbnail(gif_file)
342
+ end
343
+
344
+ def cloud_name_to_graph_name(cloud)
345
+ UrlGraph.tag2file(cloud) + '__go'
346
+ end
347
+
348
+ def write_all_overviews
349
+ # Create a file containing a cloud of all tags
350
+ list = @links_by_tag.keys.sort
351
+ filename = 'index.htm'
352
+ cloud_name = 'cloud (all)'
353
+ write_overview(filename, cloud_name, list, @bundles, @links_by_tag)
354
+ do_graphviz(@tag_graph, cloud_name)
355
+
356
+ # The same for all bundles
357
+ @bundles.keys.each do
358
+ | bundle |
359
+ list = @bundles[bundle]
360
+ filename = "bundle_#{bundle}.htm"
361
+ cloud_name = bundle
362
+ write_overview(filename, cloud_name, list, @bundles, @links_by_tag)
363
+ @tag_graph.filter_set = Set.new(list)
364
+ do_graphviz(@tag_graph, cloud_name)
365
+ @tag_graph.filter_set = nil
366
+ end
367
+ end
368
+
369
+ # Create one Html-File for each tag
370
+ def write_single_tag_files
371
+ @links_by_tag.keys.sort.each do
372
+ |tag|
373
+ File.open(File.join(@html_output_path, UrlGraph.node_url(tag)), 'w') do
374
+ |fp|
375
+ write_html_head_on(fp)
376
+ fp.puts "<h1>Tag: #{tag}</h1>"
377
+ link_index_on(fp, 'none', @bundles)
378
+ fp.puts '<div class="main">'
379
+ fp.puts "<h3>Subcategories for tag #{tag}</h3>"
380
+ subcloud = @tag_graph.adjacent_vertices(tag).sort
381
+ max_num_links = subcloud.collect{ |n| @links_by_edge[UrlGraph.edge_name(tag, n)].size }.max
382
+ subcloud.each do
383
+ |neighbor|
384
+ edge_name = UrlGraph.edge_name(tag, neighbor)
385
+ edgePage = UrlGraph.edge_url(tag, neighbor)
386
+ style = get_cloud_style(@links_by_edge[edge_name].size, max_num_links)
387
+ fp.puts(UrlGraph.intern_ankor(UrlGraph.edge_url(tag, neighbor), neighbor, style))
388
+ end
389
+ list = @links_by_tag[tag].select { |l| l['tags'].size == 1 }
390
+ unless list.empty?
391
+ fp.puts("<h3>Links with the single tag #{tag}</h3>")
392
+ fp.puts('<ul>')
393
+ sorted_links(list).each do
394
+ |link|
395
+ fp.puts('<li>')
396
+ write_link_on(link, fp, [tag])
397
+ fp.puts('</li>')
398
+ end
399
+ fp.puts('</ul>')
400
+ end
401
+ if @link_to_delicious_if_possible
402
+ fp.puts("<h3>#{UrlGraph.ankor(DEL_ICIO_US_URL, 'del.icio.us')} links with tag #{tag}</h3>")
403
+ fp.puts(UrlGraph.ankor(delicious_user_url(tag), "#{@user}"))
404
+ fp.puts(UrlGraph.ankor(delicious_tag_url(tag), "all"))
405
+ end
406
+ fp.puts '</div>'
407
+ write_html_tail_on(fp)
408
+ end
409
+ end
410
+ write_line_on_protokol "Wrote one html-file for each tag to directory #{@html_output_path}."
411
+ end
412
+
413
+ def write_double_tag_files
414
+ # Create one Html-File for each edge between two tags
415
+ @tag_graph.each_edge do
416
+ |tag1,tag2|
417
+ edge_name = UrlGraph.edge_name(tag1, tag2)
418
+ edge_url = UrlGraph.edge_url(tag1, tag2)
419
+ File.open(File.join(@html_output_path, edge_url), 'w') do
420
+ |fp|
421
+ write_html_head_on(fp)
422
+ fp.puts "<h1>Tags: #{tag1} AND #{tag2}</h1>"
423
+ link_index_on(fp, 'none', @bundles)
424
+ fp.puts '<div class="main">'
425
+ ankor1 = UrlGraph.node_ankor(tag1)
426
+ ankor2 = UrlGraph.node_ankor(tag2)
427
+ fp.puts "<h3>Links for tag combination #{ankor1}/#{ankor2}</h3>"
428
+ fp.puts '<ul>'
429
+ sorted_links(@links_by_edge[edge_name]).each do
430
+ |link|
431
+ fp.puts('<li>')
432
+ write_link_on(link, fp, [tag1, tag2])
433
+ fp.puts('<br></li>')
434
+ end
435
+ fp.puts '</ul>'
436
+ if @link_to_delicious_if_possible
437
+ fp.puts("<h3>#{UrlGraph.ankor(DEL_ICIO_US_URL, 'del.icio.us')} links with tag combination #{ankor1}/#{ankor2}</h3>")
438
+ fp.puts(UrlGraph.ankor(delicious_user_url(tag1, tag2), @user))
439
+ fp.puts(UrlGraph.ankor(delicious_tag_url(tag1, tag2), 'all'))
440
+ end
441
+ fp.puts '</div>'
442
+ write_html_tail_on(fp)
443
+ end
444
+ end
445
+ write_line_on_protokol "Wrote one html-file for each combination of two tags to directory #{@html_output_path}."
446
+ end
447
+
448
+ # make all run-variables garbage collectable
449
+ def reset_build_state
450
+ @tag_graph = nil
451
+ @links_by_tag = nil
452
+ @links_by_edge = nil
453
+ @bundles = nil
454
+ end
455
+
456
+ # run the complete batch
457
+ def run
458
+ unless File.exists?(@input_file)
459
+ raise "Missing input file #{@input_file}"
460
+ end
461
+ check_prerequisites
462
+ Dir.mkdir(@html_output_path) unless File.directory?(@html_output_path)
463
+ copy_style_to_user unless @user == @style
464
+ init_tag_graph
465
+ load_links
466
+ write_all_overviews
467
+ write_single_tag_files
468
+ write_double_tag_files
469
+ reset_build_state
470
+ write_line_on_protokol "Finished."
471
+ end
472
+ end
473
+
474
+ if $0 == __FILE__
475
+ YamlLinksToHtml.new.run
476
+ end