tiddlywiki_cp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +674 -0
- data/History.txt +4 -0
- data/Manifest.txt +68 -0
- data/README.txt +3 -0
- data/Rakefile +123 -0
- data/bin/tiddlywiki_cp +13 -0
- data/lib/tiddlywiki_cp/converters.rb +36 -0
- data/lib/tiddlywiki_cp/file2file.rb +32 -0
- data/lib/tiddlywiki_cp/file2tiddler.rb +42 -0
- data/lib/tiddlywiki_cp/r4tw.rb +763 -0
- data/lib/tiddlywiki_cp/tiddler2directory.rb +27 -0
- data/lib/tiddlywiki_cp/tiddler2file.rb +41 -0
- data/lib/tiddlywiki_cp/tiddler2tiddlywiki.rb +29 -0
- data/lib/tiddlywiki_cp/tiddler_css.rb +39 -0
- data/lib/tiddlywiki_cp/tiddler_html.rb +39 -0
- data/lib/tiddlywiki_cp/tiddler_js.rb +39 -0
- data/lib/tiddlywiki_cp/tiddlywiki2file.rb +29 -0
- data/lib/tiddlywiki_cp/version.rb +24 -0
- data/lib/tiddlywiki_cp.rb +378 -0
- data/scripts/txt2html +67 -0
- data/setup.rb +1585 -0
- data/test/content/a +0 -0
- data/test/content/a.div +0 -0
- data/test/content/b +0 -0
- data/test/content/e +3 -0
- data/test/content/e.div +1 -0
- data/test/content/html_entities.html +6 -0
- data/test/content/test_fetch.html +6 -0
- data/test/content/universe.html +10522 -0
- data/test/r4tw/addtag.rb +93 -0
- data/test/r4tw/all.rb +29 -0
- data/test/r4tw/createfrom.rb +62 -0
- data/test/r4tw/empties/2.1.3.html +7087 -0
- data/test/r4tw/empties/2.2.0.beta5.html +8726 -0
- data/test/r4tw/fromremote.rb +19 -0
- data/test/r4tw/fromurl.rb +28 -0
- data/test/r4tw/shadows.rb +27 -0
- data/test/r4tw/tiddler.rb +70 -0
- data/test/r4tw/tiddlerfromurl.rb +23 -0
- data/test/r4tw/tiddlywiki.rb +66 -0
- data/test/r4tw/utils.rb +55 -0
- data/test/r4tw/withcontent/2.2.0.beta5.html +8739 -0
- data/test/r4tw/withcontent/22b5index.html +13523 -0
- data/test/r4tw/withcontent/empty2.html +7084 -0
- data/test/r4tw/withcontent/nothing.js +1 -0
- data/test/r4tw/write_all_tiddlers_to.rb +62 -0
- data/test/test_all.rb +8 -0
- data/test/test_helper.rb +36 -0
- data/test/test_tiddler_css.rb +55 -0
- data/test/test_tiddler_html.rb +54 -0
- data/test/test_tiddler_js.rb +56 -0
- data/test/test_tiddlywiki_cp.rb +341 -0
- data/website/files/DefaultTiddlers.tiddler +2 -0
- data/website/files/DefaultTiddlers.tiddler.div +1 -0
- data/website/files/Introduction.tiddler +12 -0
- data/website/files/Introduction.tiddler.div +1 -0
- data/website/files/MainMenu.tiddler +1 -0
- data/website/files/MainMenu.tiddler.div +1 -0
- data/website/files/SiteSubtitle.tiddler +1 -0
- data/website/files/SiteSubtitle.tiddler.div +1 -0
- data/website/files/SiteTitle.tiddler +1 -0
- data/website/files/SiteTitle.tiddler.div +1 -0
- data/website/files/Usage.tiddler +55 -0
- data/website/files/Usage.tiddler.div +1 -0
- data/website/files/WebDAVSavingPlugin.js +234 -0
- data/website/files/WebDAVSavingPlugin.js.div +1 -0
- data/website/index.html +9144 -0
- data/website/index.xml +336 -0
- metadata +116 -0
@@ -0,0 +1,763 @@
|
|
1
|
+
#
|
2
|
+
# =r4tw
|
3
|
+
# Author:: Simon Baird
|
4
|
+
# URL:: http://simonbaird.com/r4tw
|
5
|
+
# License:: http://en.wikipedia.org/wiki/MIT_license
|
6
|
+
# r4tw is some ruby classes for manipuating TiddlyWikis and tiddlers.
|
7
|
+
# It is similar to cook and ginsu but cooler.
|
8
|
+
#
|
9
|
+
# <i>$Rev: 2238 $</i>
|
10
|
+
#
|
11
|
+
# ===Known problems
|
12
|
+
# from_remote_tw can be problematic if importing from a 2.1 TW into a 2.2 TW.
|
13
|
+
#
|
14
|
+
|
15
|
+
|
16
|
+
#---------------------------------------------------------------------
|
17
|
+
#-- General purpose utils
|
18
|
+
|
19
|
+
require 'pathname'
|
20
|
+
require 'open-uri'
|
21
|
+
|
22
|
+
def read_file(file_name) #:nodoc:
|
23
|
+
File.read(file_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch_url(url) #:nodoc:
|
27
|
+
open(url).read.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def this_dir(this_file=$0) #:nodoc:
|
31
|
+
Pathname.new(this_file).expand_path.dirname
|
32
|
+
end
|
33
|
+
|
34
|
+
class String
|
35
|
+
def to_file(file_name) #:nodoc:
|
36
|
+
File.open(file_name,"w") { |f| f << self }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
#---------------------------------------------------------------------
|
42
|
+
#-- TiddlyWiki related utils
|
43
|
+
|
44
|
+
class String
|
45
|
+
|
46
|
+
def escapeLineBreaks
|
47
|
+
gsub(/\\/m,"\\s").gsub(/\n/m,"\\n").gsub(/\r/m,"")
|
48
|
+
end
|
49
|
+
|
50
|
+
def unescapeLineBreaks
|
51
|
+
# not sure what \b is for
|
52
|
+
gsub(/\\n/m,"\n").gsub(/\\b/m," ").gsub(/\\s/,"\\").gsub(/\r/m,"")
|
53
|
+
end
|
54
|
+
|
55
|
+
def encodeHTML
|
56
|
+
gsub(/&/m,"&").gsub(/</m,"<").gsub(/>/m,">").gsub(/\"/m,""")
|
57
|
+
end
|
58
|
+
|
59
|
+
def decodeHTML
|
60
|
+
gsub(/&/m,"&").gsub(/</m,"<").gsub(/>/m,">").gsub(/"/m,"\"")
|
61
|
+
end
|
62
|
+
|
63
|
+
def readBrackettedList
|
64
|
+
# scan is a beautiful thing
|
65
|
+
scan(/\[\[([^\]]+)\]\]|(\S+)/).map {|m| m[0]||m[1]}
|
66
|
+
end
|
67
|
+
|
68
|
+
def toBrackettedList
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
class Array
|
75
|
+
|
76
|
+
def toBrackettedList
|
77
|
+
map{ |i| (i =~ /\s/) ? ("[["+i+"]]") : i }.join(" ")
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
class Time
|
83
|
+
|
84
|
+
def convertToLocalYYYYMMDDHHMM()
|
85
|
+
self.localtime.strftime("%Y%m%d%H%M")
|
86
|
+
end
|
87
|
+
|
88
|
+
def convertToYYYYMMDDHHMM()
|
89
|
+
self.utc.strftime("%Y%m%d%H%M")
|
90
|
+
end
|
91
|
+
|
92
|
+
def Time.convertFromYYYYMMDDHHMM(date_string)
|
93
|
+
m = date_string.match(/(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/)
|
94
|
+
Time.utc(m[1],m[2],m[3],m[4],m[5])
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
#---------------------------------------------------------------------
|
101
|
+
# Tiddler
|
102
|
+
#
|
103
|
+
|
104
|
+
# =Tiddler
|
105
|
+
# For creating and manipulating tiddlers
|
106
|
+
# ===Example
|
107
|
+
# puts Tiddler.new({'tiddler'=>'Hello','text'=>'Hi there','tags'=>['tag1','tag2']})
|
108
|
+
|
109
|
+
class Tiddler
|
110
|
+
|
111
|
+
@@main_fields = %w[tiddler modifier modified created tags]
|
112
|
+
# and soon to be changecount?
|
113
|
+
|
114
|
+
# text is not really a field in TiddlyWiki it makes
|
115
|
+
# things easier to make it one here. It could possibly
|
116
|
+
# clash with a real field called text. Ignore this fact for now...
|
117
|
+
|
118
|
+
@@defaults = {
|
119
|
+
'tiddler' => 'New Tiddler',
|
120
|
+
'modified' => Time.now.convertToYYYYMMDDHHMM,
|
121
|
+
'created' => Time.now.convertToYYYYMMDDHHMM,
|
122
|
+
'modifier' => 'YourName',
|
123
|
+
'tags' => '',
|
124
|
+
'text' => '',
|
125
|
+
}
|
126
|
+
|
127
|
+
# used by from_file
|
128
|
+
@@default_ext_tag_map = {
|
129
|
+
'.js' => %[systemConfig],
|
130
|
+
'.html' => %[html],
|
131
|
+
'.css' => %[css],
|
132
|
+
'.pub' => %[contentPublisher],
|
133
|
+
'.palette' => %[palette],
|
134
|
+
}
|
135
|
+
|
136
|
+
attr_accessor :fields
|
137
|
+
|
138
|
+
# Depending on the arguments this can be used to create or import a tiddler in various ways.
|
139
|
+
#
|
140
|
+
# ===From scratch
|
141
|
+
# If the argument is a Hash then it is used to specify a tiddler to be created from
|
142
|
+
# scratch.
|
143
|
+
#
|
144
|
+
# Example:
|
145
|
+
# t = Tiddler.new.from({
|
146
|
+
# 'tiddler'=>'HelloThere',
|
147
|
+
# 'text'=>'And welcome',
|
148
|
+
# })
|
149
|
+
# Other built-in fields are +modified+, +created+, +modifier+ and +tags+. Any other
|
150
|
+
# fields you add will be created as tiddler extended fields. Text is the contents of
|
151
|
+
# the tiddler. Tiddler is the title of the tiddler.
|
152
|
+
#
|
153
|
+
#
|
154
|
+
# ===From a file
|
155
|
+
# If the argument looks like a file name (ie a string that doesn't match the other
|
156
|
+
# criteria then create a tiddler with the name being the file name and the
|
157
|
+
# contents being the contents of the file. Does some guessing about tags based on
|
158
|
+
# the file's extension. (This is customisable, see code for details). Also reads the
|
159
|
+
# file modified date and uses it.
|
160
|
+
#
|
161
|
+
# Example:
|
162
|
+
# t = Tiddler.new.from("myplugin.js")
|
163
|
+
#
|
164
|
+
# ===From a TiddlyWiki
|
165
|
+
# If the argument is in the form file.html#TiddlerName or http://sitename.com/#TiddlerName
|
166
|
+
# then import TiddlerName from the specified location
|
167
|
+
#
|
168
|
+
# Example:
|
169
|
+
# t1 = Tiddler.new.from("myfile.html#SomeTiddler")
|
170
|
+
# t2 = Tiddler.new.from("http://www.tiddlywiki.com/#HelloThere")
|
171
|
+
#
|
172
|
+
#
|
173
|
+
# ===From a url
|
174
|
+
# Creates a tiddler from a url. The entire contents of the page are the contents
|
175
|
+
# of the tiddler. You should set the 'tiddler' field and other fields using a hash
|
176
|
+
# as the second argument in the same format as creating a tiddler from scratch.
|
177
|
+
# There is no automatic tagging for this one so you should add tags yourself as required
|
178
|
+
#
|
179
|
+
# Example:
|
180
|
+
# t = Tiddler.new.from(
|
181
|
+
# "http://svn.somewhere.org/Trunk/HelloWorld.js",
|
182
|
+
# {'tiddler'=>'HelloWorld','tags'=>'systemConfig'}
|
183
|
+
# )
|
184
|
+
#
|
185
|
+
#
|
186
|
+
# ===From a div string
|
187
|
+
# If the argument is a string containing a tiddler div such
|
188
|
+
# as would be found in a TiddlyWiki storeArea then the tiddler
|
189
|
+
# is created from that div
|
190
|
+
#
|
191
|
+
def initialize(*args)
|
192
|
+
@fields = {}
|
193
|
+
|
194
|
+
case args[0]
|
195
|
+
when Hash
|
196
|
+
from_scratch(*args)
|
197
|
+
|
198
|
+
when Tiddler
|
199
|
+
from_tiddler(*args)
|
200
|
+
|
201
|
+
when /^\s*<div/
|
202
|
+
from_div(*args)
|
203
|
+
|
204
|
+
when /#/
|
205
|
+
from_tiddler(from_tw(*args))
|
206
|
+
|
207
|
+
when /^(ftp|http|file):/
|
208
|
+
from_url(*args)
|
209
|
+
|
210
|
+
when String
|
211
|
+
from_file(*args)
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
# Intende to become private but not yet because
|
219
|
+
# all the test units use them
|
220
|
+
#
|
221
|
+
#private
|
222
|
+
|
223
|
+
def from_tiddler(other_tiddler)
|
224
|
+
@fields = {}
|
225
|
+
@fields.update(other_tiddler.fields)
|
226
|
+
end
|
227
|
+
|
228
|
+
def from_scratch(fields={}) #:nodoc:
|
229
|
+
@fields = @@defaults.merge(fields)
|
230
|
+
@fields['tags'] &&= @fields['tags'].toBrackettedList # in case it's an array
|
231
|
+
self
|
232
|
+
end
|
233
|
+
|
234
|
+
def from_div(div_str,use_pre=false) #:nodoc:
|
235
|
+
match_data = div_str.match(/<div([^>]+)>(.*?)<\/div>/m)
|
236
|
+
field_str = match_data[1]
|
237
|
+
text_str = match_data[2]
|
238
|
+
|
239
|
+
field_str.scan(/ ([\w\.]+)="([^"]+)"/) do |field_name,field_value|
|
240
|
+
if field_name == "title"
|
241
|
+
field_name = "tiddler"
|
242
|
+
end
|
243
|
+
@fields[field_name] = field_value
|
244
|
+
end
|
245
|
+
|
246
|
+
text_str.sub!(/\n<pre>/,'')
|
247
|
+
text_str.sub!(/<\/pre>\n/,'')
|
248
|
+
|
249
|
+
if (use_pre)
|
250
|
+
@fields['text'] = text_str.decodeHTML
|
251
|
+
else
|
252
|
+
@fields['text'] = text_str.unescapeLineBreaks.decodeHTML
|
253
|
+
end
|
254
|
+
|
255
|
+
self
|
256
|
+
end
|
257
|
+
|
258
|
+
def from_file(file_name, fields={}, ext_tag_map=@@default_ext_tag_map) #:nodoc:
|
259
|
+
ext = File.extname(file_name)
|
260
|
+
base = File.basename(file_name,ext)
|
261
|
+
@fields = @@defaults.merge(fields)
|
262
|
+
@fields['tiddler'] = base
|
263
|
+
@fields['text'] = read_file(file_name)
|
264
|
+
@fields['created'] = File.mtime(file_name).convertToYYYYMMDDHHMM
|
265
|
+
# @fields['modified'] = @fields['created']
|
266
|
+
@fields['tags'] = ext_tag_map[ext].toBrackettedList if ext_tag_map[ext]
|
267
|
+
self
|
268
|
+
end
|
269
|
+
|
270
|
+
def from_url(url,fields={}) #:nodoc:
|
271
|
+
@fields = @@defaults.merge(fields)
|
272
|
+
@fields['text'] = fetch_url(url)
|
273
|
+
self
|
274
|
+
end
|
275
|
+
|
276
|
+
def from_tw(tiddler_url) #:nodoc:
|
277
|
+
# this works if url is a local file, eg "somefile.html#TiddlerName"
|
278
|
+
# as well as if it's a remote file, eg "http://somewhere.com/#TiddlerName"
|
279
|
+
location,tiddler_name = tiddler_url.split("#")
|
280
|
+
TiddlyWiki.new.source_empty(location).get_tiddler(tiddler_name)
|
281
|
+
end
|
282
|
+
|
283
|
+
alias from_remote_tw from_tw #:nodoc:
|
284
|
+
alias from_local_tw from_tw #:nodoc:
|
285
|
+
|
286
|
+
# Returns a hash containing the tiddlers extended fields
|
287
|
+
# Probably would include changecount at this stage at least
|
288
|
+
def extended_fields
|
289
|
+
@fields.keys.reject{ |f| @@main_fields.include?(f) || f == 'text' }.sort
|
290
|
+
end
|
291
|
+
|
292
|
+
# Converts to a div suitable for a TiddlyWiki store area
|
293
|
+
def to_fields_string(use_pre=false)
|
294
|
+
|
295
|
+
@@main_fields.
|
296
|
+
reject{ |f|
|
297
|
+
use_pre and (
|
298
|
+
# seems like we have to leave out modified if there is none
|
299
|
+
(f == 'modified' and !@fields[f]) or
|
300
|
+
# seems like we have to not print tags="" any more
|
301
|
+
(f == 'tags' and (!@fields[f] or @fields[f].length == 0))
|
302
|
+
)
|
303
|
+
}.
|
304
|
+
map { |f|
|
305
|
+
# support old style tiddler=""
|
306
|
+
# and new style title=""
|
307
|
+
if f == 'tiddler' and use_pre
|
308
|
+
field_name = 'title'
|
309
|
+
else
|
310
|
+
field_name = f
|
311
|
+
end
|
312
|
+
%{#{field_name}="#{@fields[f]}"}
|
313
|
+
} +
|
314
|
+
extended_fields.
|
315
|
+
map{ |f| %{#{f}="#{@fields[f]}"} }
|
316
|
+
end
|
317
|
+
|
318
|
+
def to_s(use_pre=false)
|
319
|
+
fields_string = to_fields_string(use_pre)
|
320
|
+
if use_pre
|
321
|
+
"<div #{fields_string.join(' ')}>\n<pre>#{@fields['text'].encodeHTML}</pre>\n</div>"
|
322
|
+
else
|
323
|
+
"<div #{fields_string.join(' ')}>#{@fields['text'].escapeLineBreaks.encodeHTML}</div>"
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
|
328
|
+
alias to_div to_s #:nodoc:
|
329
|
+
|
330
|
+
# Lets you access fields like this:
|
331
|
+
# tiddler.name
|
332
|
+
# tiddler.created
|
333
|
+
# etc
|
334
|
+
#
|
335
|
+
def method_missing(method,*args)
|
336
|
+
|
337
|
+
method = method.to_s
|
338
|
+
|
339
|
+
synonyms = {
|
340
|
+
'name' => 'tiddler',
|
341
|
+
'title' => 'tiddler',
|
342
|
+
'content' => 'text',
|
343
|
+
'body' => 'text',
|
344
|
+
}
|
345
|
+
|
346
|
+
method = synonyms[method] || method
|
347
|
+
|
348
|
+
if @@main_fields.include? method or @fields[method]
|
349
|
+
@fields[method]
|
350
|
+
else
|
351
|
+
raise "No such field or method #{method}"
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
# Add some text to the end of a tiddler's content
|
357
|
+
def append_content(new_content)
|
358
|
+
@fields['text'] += new_content
|
359
|
+
self
|
360
|
+
end
|
361
|
+
|
362
|
+
# Add some text to the beginning of a tiddler's content
|
363
|
+
def prepend_content(new_content)
|
364
|
+
@fields['text'] = new_content + @fields['text']
|
365
|
+
self
|
366
|
+
end
|
367
|
+
|
368
|
+
# Renames a tiddler
|
369
|
+
def rename(new_name)
|
370
|
+
@fields['tiddler'] = new_name
|
371
|
+
self
|
372
|
+
end
|
373
|
+
|
374
|
+
# Makes a copy of this tiddler
|
375
|
+
def copy
|
376
|
+
Tiddler.new.from_div(self.to_div)
|
377
|
+
end
|
378
|
+
|
379
|
+
# Makes a copy of this tiddler with a new title
|
380
|
+
def copy_to(new_title)
|
381
|
+
copy.rename(new_title)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Adds a tag
|
385
|
+
def add_tag(new_tag)
|
386
|
+
@fields['tags'] = @fields['tags'].
|
387
|
+
readBrackettedList.
|
388
|
+
push(new_tag).
|
389
|
+
uniq.
|
390
|
+
toBrackettedList
|
391
|
+
|
392
|
+
self
|
393
|
+
end
|
394
|
+
|
395
|
+
# Adds a list of tags
|
396
|
+
def add_tags(tags)
|
397
|
+
tags.each { |tag| add_tag(tag) }
|
398
|
+
end
|
399
|
+
|
400
|
+
# Removes a single tag
|
401
|
+
def remove_tag(old_tag)
|
402
|
+
@fields['tags'] = @fields['tags'].
|
403
|
+
readBrackettedList.
|
404
|
+
reject { |tag| tag == old_tag }.
|
405
|
+
toBrackettedList
|
406
|
+
|
407
|
+
self
|
408
|
+
end
|
409
|
+
|
410
|
+
# Removes a list of tags
|
411
|
+
def remove_tags(tags)
|
412
|
+
tags.each { |tag| remove_tags(tag) }
|
413
|
+
end
|
414
|
+
|
415
|
+
# Returns true if a tiddler has a particular tag
|
416
|
+
def has_tag(tag)
|
417
|
+
fields['tags'] && fields['tags'].readBrackettedList.include?(tag)
|
418
|
+
end
|
419
|
+
|
420
|
+
# Returns a Hash containing all tiddler slices
|
421
|
+
def get_slices
|
422
|
+
if not @slices
|
423
|
+
@slices = {}
|
424
|
+
# look familiar?
|
425
|
+
slice_re = /(?:[\'\/]*~?(\w+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?(\w+)\:?[\'\/]*\|\s*(.*?)\s*\|)/m
|
426
|
+
text.scan(slice_re).each do |l1,v1,l2,v2|
|
427
|
+
@slices[l1||l2] = v1||v2;
|
428
|
+
end
|
429
|
+
end
|
430
|
+
@slices
|
431
|
+
end
|
432
|
+
|
433
|
+
# Returns a tiddler slice
|
434
|
+
def get_slice(slice)
|
435
|
+
get_slices[slice]
|
436
|
+
end
|
437
|
+
|
438
|
+
#
|
439
|
+
# Experimental. Provides access to plugin meta slices.
|
440
|
+
# Returns one meta value or a hash of them if no argument is given
|
441
|
+
#
|
442
|
+
def plugin_meta(slice=nil)
|
443
|
+
# see http://www.tiddlywiki.com/#ExamplePlugin
|
444
|
+
if not @plugin_meta
|
445
|
+
meta = %w[Name Description Version Date Source Author License CoreVersion Browser]
|
446
|
+
@plugin_meta = get_slices.reject{|k,v| not meta.include?(k)}
|
447
|
+
end
|
448
|
+
if slice
|
449
|
+
@plugin_meta[slice]
|
450
|
+
else
|
451
|
+
@plugin_meta
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|
456
|
+
|
457
|
+
#---------------------------------------------------------------------
|
458
|
+
# =Tiddlywiki
|
459
|
+
# Create and manipulate TiddlyWiki files
|
460
|
+
#
|
461
|
+
|
462
|
+
class TiddlyWiki
|
463
|
+
|
464
|
+
attr_accessor :orig_tiddlers, :tiddlers, :raw
|
465
|
+
|
466
|
+
# doesn't do much. probably should allow an empty file param
|
467
|
+
def initialize(use_pre=false)
|
468
|
+
@use_pre = use_pre
|
469
|
+
@tiddlers = []
|
470
|
+
end
|
471
|
+
|
472
|
+
# this should replace all the add_tiddler_from_blah methods
|
473
|
+
# but actually they are still there below
|
474
|
+
# testing required
|
475
|
+
def method_missing(method_name,*args);
|
476
|
+
case method_name.to_s
|
477
|
+
when /^add_tiddler_(.*)$/
|
478
|
+
add_tiddler(Tiddler.new.send($1,*args))
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
# initialise a TiddlyWiki from a source file
|
483
|
+
# will treat empty_file as a url if it looks like one
|
484
|
+
# note that it doesn't have to be literally empty
|
485
|
+
def source_empty(empty_file,&block)
|
486
|
+
@empty_file = empty_file
|
487
|
+
if empty_file =~ /^https?/
|
488
|
+
@raw = fetch_url(@empty_file)
|
489
|
+
else
|
490
|
+
@raw = read_file(@empty_file)
|
491
|
+
end
|
492
|
+
|
493
|
+
# stupid ctrl (\r) char
|
494
|
+
#@raw.eat_ctrl_m!
|
495
|
+
|
496
|
+
if @raw =~ /var version = \{title: "TiddlyWiki", major: 2, minor: 2/
|
497
|
+
@use_pre = true
|
498
|
+
end
|
499
|
+
|
500
|
+
@core_hacks = []
|
501
|
+
@orig_tiddlers = get_orig_tiddlers
|
502
|
+
@tiddlers = @orig_tiddlers
|
503
|
+
|
504
|
+
instance_eval(&block) if block
|
505
|
+
|
506
|
+
self
|
507
|
+
end
|
508
|
+
|
509
|
+
# reads an empty from a file on disk
|
510
|
+
def source_file(file_name="empty.html")
|
511
|
+
source_empty(file_name)
|
512
|
+
end
|
513
|
+
|
514
|
+
# reads an empty file from a url
|
515
|
+
def source_url(url="http://www.tiddlywiki.com/empty.html")
|
516
|
+
source_empty(url)
|
517
|
+
end
|
518
|
+
|
519
|
+
# important regexp
|
520
|
+
# if this doesn't work we are screwed
|
521
|
+
@@store_regexp = /^(.*<div id="storeArea">\n?)(.*)(\n?<\/div>\r?\n<!--.*)$/m # stupid ctrl-m \r char
|
522
|
+
|
523
|
+
# everything before the store
|
524
|
+
# TODO make these private
|
525
|
+
def pre_store #:nodoc:
|
526
|
+
@raw.sub(@@store_regexp,'\1')
|
527
|
+
end
|
528
|
+
|
529
|
+
# the store itself
|
530
|
+
def store #:nodoc:
|
531
|
+
@raw.sub(@@store_regexp,'\2')
|
532
|
+
end
|
533
|
+
|
534
|
+
# everything after the store
|
535
|
+
def post_store #:nodoc:
|
536
|
+
@raw.sub(@@store_regexp,'\3')
|
537
|
+
end
|
538
|
+
|
539
|
+
# returns an array of tiddler divs
|
540
|
+
def tiddler_divs #:nodoc:
|
541
|
+
## the old way, one tiddler per line...
|
542
|
+
# store.strip.to_a
|
543
|
+
## the new way
|
544
|
+
store.scan(/(<div ti[^>]+>.*?<\/div>)/m).map { |m| m[0] }
|
545
|
+
# did I mention that scan is a beautiful thing?
|
546
|
+
end
|
547
|
+
|
548
|
+
# add a core hack
|
549
|
+
# it will be applied to the entire TW core like this gsub(regexp,replace)
|
550
|
+
def add_core_hack(regexp,replace)
|
551
|
+
# this is always a bad idea... ;)
|
552
|
+
@core_hacks.push([regexp,replace])
|
553
|
+
end
|
554
|
+
|
555
|
+
def get_orig_tiddlers #:nodoc:
|
556
|
+
tiddler_divs.map do |tiddler_div|
|
557
|
+
Tiddler.new.from_div(tiddler_div,@use_pre)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
# an array of tiddler titles
|
562
|
+
def tiddler_titles
|
563
|
+
@tiddlers.map { |t| t.name }
|
564
|
+
end
|
565
|
+
|
566
|
+
# returns an array of tiddlers containing a particular tag
|
567
|
+
def tiddlers_with_tag(tag)
|
568
|
+
@tiddlers.select{|t| t.has_tag(tag)}
|
569
|
+
end
|
570
|
+
|
571
|
+
# adds a tiddler
|
572
|
+
def add_tiddler(tiddler)
|
573
|
+
remove_tiddler(tiddler.name)
|
574
|
+
@tiddlers << tiddler
|
575
|
+
tiddler
|
576
|
+
end
|
577
|
+
|
578
|
+
|
579
|
+
# removes a tiddler by name
|
580
|
+
def remove_tiddler(tiddler_name)
|
581
|
+
@tiddlers.reject!{|t| t.name == tiddler_name}
|
582
|
+
end
|
583
|
+
|
584
|
+
# adds a shadow tiddler
|
585
|
+
# note that tags and other fields aren't preserved
|
586
|
+
def add_shadow_tiddler(tiddler)
|
587
|
+
# shadow tiddlers currently implemented as core_hacks
|
588
|
+
add_core_hack(
|
589
|
+
/^\/\/ End of scripts\n/m,
|
590
|
+
"\\0\nconfig.shadowTiddlers[\"#{tiddler.name}\"] = #{tiddler.text.dump};\n\n"
|
591
|
+
)
|
592
|
+
end
|
593
|
+
|
594
|
+
# adds a shadow tiddler from a file
|
595
|
+
def add_shadow_tiddler_from_file(file_name)
|
596
|
+
add_shadow_tiddler Tiddler.new.from_file("#{file_name}")
|
597
|
+
end
|
598
|
+
|
599
|
+
# add tiddlers from a list of file names
|
600
|
+
# ignores file names starting with #
|
601
|
+
# so you can do this
|
602
|
+
# %w[
|
603
|
+
# foo
|
604
|
+
# bar
|
605
|
+
# #baz
|
606
|
+
# ]
|
607
|
+
# and it will skip baz
|
608
|
+
def add_tiddlers(file_names)
|
609
|
+
file_names.reject{|f| f.match(/^#/)}.each do |f|
|
610
|
+
add_tiddler_from_file(f)
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
def add_tiddler_from(*args)
|
615
|
+
add_tiddler Tiddler.new(*args)
|
616
|
+
end
|
617
|
+
|
618
|
+
def add_tiddlers_from(tiddler_list)
|
619
|
+
tiddler_list.each { |t| add_tiddler Tiddler.new(t) }
|
620
|
+
end
|
621
|
+
|
622
|
+
|
623
|
+
# add tiddlers from files found in directory dir_name
|
624
|
+
# TODO exclude pattern?
|
625
|
+
def add_tiddlers_from_dir(dir_name)
|
626
|
+
add_tiddlers(Dir.glob("#{dir_name}/*"))
|
627
|
+
end
|
628
|
+
|
629
|
+
# add shadow tiddlers from files found in directory dir_name
|
630
|
+
def add_shadow_tiddlers_from_dir(dir_name)
|
631
|
+
Dir.glob("#{dir_name}/*").each do |f|
|
632
|
+
add_shadow_tiddler_from_file(f)
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
# add a list of files found in dir_name packaged as javascript to create shadow tiddlers
|
637
|
+
# append the javascript to the contents of file_name
|
638
|
+
# (needs more explanation perhaps)
|
639
|
+
# see also package_as
|
640
|
+
def package_as_from_dir(file_name,dir_name)
|
641
|
+
package_as(file_name,Dir.glob("#{dir_name}/*"))
|
642
|
+
end
|
643
|
+
|
644
|
+
# if you have a file containing just tiddler divs you can read them
|
645
|
+
# all in with this
|
646
|
+
def add_tiddlers_from_file(file_name)
|
647
|
+
# a file full of divs
|
648
|
+
File.read(file_name).to_a.inject([]) do |tiddlers,tiddler_div|
|
649
|
+
@tiddlers << Tiddler.new.from_div(tiddler_div,@use_pre)
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
# get a tidler by name
|
654
|
+
def get_tiddler(tiddler_title)
|
655
|
+
@tiddlers.select{|t| t.name == tiddler_title}.first
|
656
|
+
end
|
657
|
+
|
658
|
+
# output the TiddlyWiki file
|
659
|
+
def to_s
|
660
|
+
pre_store_hacked = pre_store
|
661
|
+
post_store_hacked = post_store
|
662
|
+
@core_hacks.each do |hack|
|
663
|
+
pre_store_hacked.gsub!(hack[0],hack[1])
|
664
|
+
post_store_hacked.gsub!(hack[0],hack[1])
|
665
|
+
end
|
666
|
+
"#{pre_store_hacked}#{store_to_s}#{post_store_hacked}"
|
667
|
+
end
|
668
|
+
|
669
|
+
# output just the contents of the store
|
670
|
+
def store_to_s
|
671
|
+
# not sure about this bit. breaks some tests if I put it in
|
672
|
+
#((@use_pre and @tiddlers.length > 0) ? "\n" : "") +
|
673
|
+
@tiddlers.sort_by{|t| t.name}.inject(""){ |out,t|out << t.to_div(@use_pre) << "\n"}
|
674
|
+
end
|
675
|
+
|
676
|
+
# writes just the store area to a file
|
677
|
+
# the file can be used with ImportTiddlers to save download bandwidth
|
678
|
+
def store_to_file(file_name)
|
679
|
+
File.open(file_name,"w") { |f| f << "<div id=\"storeArea\">\n#{store_to_s}</div>" }
|
680
|
+
puts "Wrote store only to '#{file_name}'"
|
681
|
+
end
|
682
|
+
|
683
|
+
# writes just the store contents to a file
|
684
|
+
def store_to_divs(file_name)
|
685
|
+
File.open(file_name,"w") { |f| f << store_to_s }
|
686
|
+
puts "Wrote tiddlers only to '#{file_name}'"
|
687
|
+
end
|
688
|
+
|
689
|
+
# writes the entire TiddlyWiki to a file
|
690
|
+
def to_file(file_name)
|
691
|
+
File.open(file_name,"w") { |f| f << to_s }
|
692
|
+
# puts "Wrote tw file to '#{file_name}'"
|
693
|
+
end
|
694
|
+
|
695
|
+
# takes a list of file_names, reads their content
|
696
|
+
# and converts them to javascript creation of shadow tiddlers
|
697
|
+
# then appends that to the contents of file_name
|
698
|
+
# (sorry, confusing)
|
699
|
+
def package_as(file_name,package_file_names)
|
700
|
+
new_tiddler = add_tiddler Tiddler.new.from_file(file_name)
|
701
|
+
new_tiddler.append_content(package(package_file_names))
|
702
|
+
# date of the most recently modified
|
703
|
+
new_tiddler.fields['modified'] = package_file_names.push(file_name).map{|f| File.mtime(f)}.max.convertToYYYYMMDDHHMM
|
704
|
+
end
|
705
|
+
|
706
|
+
# TODO make private?
|
707
|
+
def package(file_names) #:nodoc:
|
708
|
+
"//{{{\nmerge(config.shadowTiddlers,{\n\n"+
|
709
|
+
((file_names.map do |f|
|
710
|
+
Tiddler.new.from_file(f)
|
711
|
+
end).map do |t|
|
712
|
+
"'" + t.name + "':[\n " +
|
713
|
+
t.text.chomp.dump.gsub(/\\t/,"\t").gsub(/\\n/,"\",\n \"").gsub(/\\#/,"#") + "\n].join(\"\\n\")"
|
714
|
+
end).join(",\n\n")+
|
715
|
+
"\n\n});\n//}}}\n"
|
716
|
+
end
|
717
|
+
|
718
|
+
# copy all tiddlers from another TW file into this TW
|
719
|
+
# good for creating Tiddlyspot flavours
|
720
|
+
def copy_all_tiddlers_from(file_name)
|
721
|
+
TiddlyWiki.new.source_empty(file_name).tiddlers.each do |t|
|
722
|
+
add_tiddler t
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
# write all tiddlers to files in dir_name
|
727
|
+
def write_all_tiddlers_to(dir_name, div = false)
|
728
|
+
tiddlers.each do |t|
|
729
|
+
|
730
|
+
ext = 'tiddler'
|
731
|
+
|
732
|
+
# TODO improve this
|
733
|
+
if t.tags and t.tags.include? "systemConfig"
|
734
|
+
ext = 'js'
|
735
|
+
elsif t.name =~ /Template/
|
736
|
+
ext = 'html'
|
737
|
+
elsif t.name =~ /(StyleSheet|Styles)/
|
738
|
+
ext = 'css'
|
739
|
+
end
|
740
|
+
|
741
|
+
name = t.name.gsub('/', '%2F')
|
742
|
+
file = "#{dir_name}/#{name}.#{ext}"
|
743
|
+
if div
|
744
|
+
t.to_fields_string(@use_pre).join(' ').to_file("#{file}.div")
|
745
|
+
end
|
746
|
+
t.text.to_file(file)
|
747
|
+
modified = t.modified ? Time.convertFromYYYYMMDDHHMM(t.modified) : Time.now
|
748
|
+
File.utime(modified, modified, file)
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
end
|
753
|
+
|
754
|
+
#
|
755
|
+
# A short hand for DSL style TiddlyWiki creation. Takes a block of TiddlyWiki methods that get instance_eval'ed
|
756
|
+
#
|
757
|
+
def make_tw(source=nil,&block)
|
758
|
+
tw = TiddlyWiki.new
|
759
|
+
tw.source_empty(source) if source
|
760
|
+
tw.instance_eval(&block) if block
|
761
|
+
tw
|
762
|
+
end
|
763
|
+
|