-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathblogmax.el
2361 lines (2156 loc) · 89.5 KB
/
blogmax.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; blogmax.el - maintain a weblog <pre>
;; Copyright (C) 2001-2007 Bill St. Clair
;; email: [email protected]
;; Web: http://billstclair.com/blogmax/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Mod History
;;
;; 070219 wws weblog-make-rss now includes <link> and <title>, and does
;; a better job with the GUID.
;; 070127 wws weblog-story-file-p returns true for file names containing
;; non-digits
;; 061220 wws weblog-insert-centered-image
;; 060117 wws weblog-upload doesn't upload if *weblog-ftp-directory*
;; is blank or null.
;; 051101 wws weblog-latest-month-before works correctly when there are
;; future files in the directory.
;; 050605 wws Make it work to store the day files in year directories
;; (two digits matching the first two digits of the file name).
;; 050124 wws weblog-set-buffer-mode no longer searches for weblog.ini
;; unless the extension of the buffer's file name is "txt".
;; This stops long pauses on ange-ftp connections looking
;; for source files.
;; 041208 wws Add "GUID" to RSS so that it will work correctly with
;; http://minutillo.com/steve/feedonfeeds/
;; This is a kludge that generates new GUIDs every time
;; you upload the RSS file, but it works for me.
;; weblog-upload-rss invoked interactively now takes a prefix
;; argument. If 1 (the default), doesn't upload the RSS
;; file to the FTP server.
;; Add Shane Simmons' weblog-macro-wikipedia
;; 040914 wws {bumper back top-fore top-back top-msg bot-fore bot-back bot-msg}
;; Generates a bumper sticker with a top and bottom section.
;; back is the background color for the border around the
;; whole thing.
;; top-fore, top-back, & top-msg are the foreground color,
;; background color, and message for the top half.
;; bot-fore, bot-back, & bot-msg are the foreground color,
;; background color, and message for the bottom half.
;; 040904 wws {pl "..."} expansion links to day page in rss file
;; C-0 C-X C-I now properly uploads the previous month
;; and the current file, even if the "previous" month
;; is a long time back.
;; 040903 wws *weblog-bugmenot-auto-list*
;; Domains in the "bugmenot-auto-list" get {bugmenot "..."}
;; links auto-inserted after links to them.
;; 040903 wws {bugmenot "www.washingtonpost.com"}
;; generates a link to the Post's BugMeNot login information
;; using {blogToplevel}bugmenot.png.
;; 040328 wws weblog-make-index always generates *weblog-index-files*
;; entries, independent of how many future days are
;; in the directory.
;; 040208 m3m Make weblog-rss-format-time RFC 822 compliant
;; 040125 wws Add optional link-text param to weblog-macro-dailylink
;; Other small changes to help CSS templates work.
;; 040103 wws {pl "name"} (permalink macro)
;; Make weblog-insert-day-index-entries use the last line
;; of a multi-line header, so that it won't include
;; a permalink macro on the line before the story link.
;; 031210 wws line-beginning-position definition for later xemacs versions
;; 031208 wws weblog-file-mdy works for month files, e.g. "0312.txt"
;; 031105 wws C-x i -> weblog-italicize-word
;; 031019 wws Peter L. DeWolf's fixes for weblog-map-all-files and
;; weblog-find-or-visit.
;; 030618 wws m-p inserts <br>. I found I didn't use c-m-b
;; 030602 wws Make weblog-month-index work correctly if the file-name
;; arg has no directory component. This happens when it's
;; called from weblog-maybe-upload-previous-month-file,
;; which caused the previous month's index to have no links
;; to days or other months.
;; 030514 wws updated weblog-macro-jargon for ESR's new directory structure
;; 030131 wws Remove title and link from RSS items
;; 030124 wws weblog-macro-jargon: www.jargon.org -> www.catb.org/jargon
;; 021222 wws C-M-R inserts "<br>"
;; 021103 wws *weblog-char-map* - automatically fix common 8-bit chars
;; 021009 wws Fix non-local return in
;; weblog-first-day-file-in-next-month.
;; Do the same in weblog-last-day-file-in-previous-month.
;; Add descending parameter to weblog-map-directory.
;; 021005 wws Tony Sidaway's changes to make the generated HTML
;; pass weblint.
;; Make weblog-month-index link to the proper next and
;; previous month in the presence of missing months.
;; 021003 wws Change GIFs to PNGs here and in shortcuts.el
;; Regenerate all the html files to use the new PNG files.
;; 020731 wws James Thornton's fix to weblog-macro-dailyLink:
;; only include the link on the index page.
;; Included his directions for using SSH via Tramp
;; as a comment to the ftp-directory spec in weblog.ini
;; 020725 wws weblog-month-index
;; Fix weblog-first-day-file-in-next-month and
;; weblog-last-day-file-in-previous-month to work
;; correctly for missing months.
;; 011130 wws weblog-insert-ellipsis bound to c-x m-.
;; 011119 wws Enable starting the calendar on any day of the week.
;; 010919 wws Better error message if shortcuts.el file doesn't parse.
;; Create shortcuts.el if it's not there.
;; 010821 wws Make it work in XEmacs 21.4 on Windoze.
;; 010803 wws Make it work in XEmacs
;; 010708 wws weblog-macro-jargon
;; 010702 wws weblog-file-in-base-dir
;; weblog-upload doesn't call weblog-make-rss unless the
;; file is in the base directory.
;; 010612 wws weblog-make-rss now generates RSS that Feedreader can
;; grok. This includes <title> & <link> tags in each <item> and
;; spaces before newlines in the <description>.
;; 010607 wws New prefix-arg values for weblog-upload-index
;; 1 - Make index. Don't upload (this is a change).
;; 0 - Make & upload index. Regen and upload this month.
;; 2 - Make & upload index. Regen and upload current directory's
;; (and subdirectories') text files.
;; 4 - Make and upload index only.
;; 010606 wws Test on Linux in Emacs 20.4.1.
;; Add missing </td> in calendar.
;; 010605 wws First release
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; To do:
;; Why doesn't C-0 C-x C-i work when there is only a single
;; file (the first day)?
;; Send XML-RPC message to rpc.weblogs.com after uploading index.
;; See http://www.xmlrpc.com/weblogsCom
;; start-process (ange-ftp-get-process)
;; process-send-string (send-string, ange-ftp-raw-send-cmd)
;; kill-buffer (ange-ftp-kill-ftp-process)
;;
;; Do the next and previous month links properly in the calendar if
;; either contains no entries but an earlier or later month does.
;;
;; Figure out how to upload a binary file (copy-file doesn't work)
;; Choose template in header comment
;; Record upload time in header comment
;; {!--Title--}
;; {!-- template template-name--}
;; {!-- saved time--}
;; content-template.tmpl only for yymmdd.txt files in
;; top-level directory. story-template.tmpl for all else.
;; Properly save an empty buffer
;; Don't do text-only operations on non ".txt" buffers.
;; Dependencies, so that generating and/or uploading one file
;; will cause files that depend on it to be generated and/or
;; uploaded.
;; Eliminate infinite recursion in macros that include files
;; This may require changing to depth first instead of
;; breadth first macro expansion.
;; This file aids in the maintenance of a Weblog.
;; It runs text files containing the weblog content through templates
;; to create HTML files, and uploads them via FTP to a web site. It is
;; similar to Dave Winer's Manila <http://manila.userland.com/>.
;; Expects the daily files to be named yymmdd.txt, where yy is the
;; last two digits of the year, mm is the month, and dd is the
;; day. These files should be at the top level of the
;; *weblog-directory*.
;;
;; weblog-save saves the current buffer to html by running it through
;; the *weblog-page-template-file*. Within that file and its included
;; templates, {contentTemplate} includes the
;; *weblog-content-template-file*, {dayTemplate} includes the
;; *weblog-day-template-file*, {storyContent} includes the current
;; buffer's text. There are other macros, but I haven't documented
;; them yet. Macro names all begin with "weblog-macro-".
;;
;; Manila used double quotes to delimit shortcuts. I'm tired of
;; escaping them, so I changed a shortcut to {=shortcut}, i.e
;; a macro beginning with "=".
;;
;; If the current buffer is not at the top-level of the
;; *weblog-directory*, then {contentTemplate} includes
;; *weblog-story-template-file* instead of
;; *weblog-content-template-file*. This allows stories to have a
;; different appearance than daily blog entries.
;;
;; weblog-make-index creates an index.html page using the last
;; *weblog-index-days* of blog entries.
;;
;; A weblog-mode, derived from html-mode, is defined near the end of
;; this file. Any ".txt" file in the *weblog-directory* or a
;; sub-directory will be opened in weblog-mode. Key bindings are
;; at the end of the file.
;;
(provide 'blogmax)
;; Use the Common Lisp library
(require 'cl)
;; Use the ange-ftp library to upload files
(if (featurep 'efs-autoloads)
(require 'efs)
(require 'ange-ftp))
;; The {calendar} macro needs some functions from the calendar library
(require 'calendar)
;; Stop xemacs from filling in a blank buffer
(set-variable 'html-helper-build-new-buffer nil)
;; The name of the parameter file containing bindings for many of
;; the variables below.
(defconst *weblog-init-file* "weblog.ini")
;; The directory containing all the files. Should end with a slash.
;; This is bound during processing to the directory containing
;; the *weblog-init-file*.
;; This initial value is designed to cause an error if any code
;; that needs it runs without some caller binding it to a directory name.
(defvar *weblog-directory* t)
;; The name of the weblog
;; Bound to the "site-name" parameter in the *weblog-init-file*.
;; Available via the {siteName} macro.
(defvar *weblog-site-name* nil)
;; The by-line that goes under the site name in the default page template.
;; Available via the {byline} macro
(defvar *weblog-byline* nil)
;; The author of the site.
;; Available via the {author} macro
(defvar *weblog-author* nil)
;; The author's email address
;; Available via the {email} macro
(defvar *weblog-email* nil)
;; The FTP directory to which to upload. Should end with a slash.
;; Of the form "/username@host:/dir-path.../" for ange-ftp upload
;; Bound to the "ftp-directory" parameter in the *weblog-init-file*.
(defvar *weblog-ftp-directory* nil)
;; The URL where the ftp directory is available vai HTTP
;; Available via the {url} macro
(defvar *weblog-url* nil)
;; The number of days to put in the index.html file
;; Bound to the "index-days" parameter in the *weblog-init-file*.
(defvar *weblog-index-days* 7)
;; This file defines "shortcuts". It contains a list of two-element
;; lists. If the first element of any list is in the text between
;; double quotes, it is replaced by the second element of the list.
;; weblog-add-shortcuts can be used to add elements to this list.
;; Bound to the "shortcuts-file" parameter in the *weblog-init-file*.
(defvar *weblog-shortcuts-file* nil)
;; True if we should generate month indices and link them into the
;; calendar.
;; Bound to the "month-index" parameter in the *weblog-init-file*
(defvar *weblog-generate-month-index-p*)
;; The text of the link generated by the {pl "name"} macro
;; Bound to the "pl-macro-text" parameter in the *weblog-init-file*
(defvar *weblog-pl-macro-text* "#")
;; A list of domains links to which will be followed by a {BugMeNot "..."}
;; link.
(defvar *weblog-bugmenot-auto-list* nil)
;; The parsed shortcuts
(defvar *weblog-shortcuts* nil)
;; Bound to the list of file names while generating the index page
(defvar *weblog-index-files* nil)
;; Bound to true while generating RSS
(defvar *weblog-generating-rss* nil)
;; This variable is bound to the story text during macro expansion
;; or to 'generate-index during index page generation.
(defvar *weblog-story-content* "")
;; Bound to the file being generated
(defvar *weblog-story-file* nil)
;; True while making the index
(defvar *weblog-making-index-p* nil)
;; The mod time of the generated file
(defvar *weblog-story-modtime* nil)
;; True if saving a story, not a day page (or the index.html file)
(defvar *weblog-saving-story* nil)
;; The template file for one day's page
;; This file's content is expanded for each generated html page.
(defconst *weblog-page-template-file* "page-template.tmpl")
;; The template for the content section of a page
;; The {contentTemplate} macro expands into the contents of this file.
(defvar *weblog-content-template-file* "content-template.tmpl")
;; The template for the content section of a non top-level page
;; The {contentTemplate} macro expands into the contents of this file.
(defconst *weblog-story-template-file* "story-template.tmpl")
;; The template file for a single day's weblog entry.
;; The {dayTemplate} macro expands into the contents of this file.
(defconst *weblog-day-template-file* "day-template.tmpl")
;; The directory for pictures
(defconst *weblog-picdir* "")
(defconst *weblog-escape-string* "\\")
(defconst *weblog-escape-char* (string-to-char *weblog-escape-string*))
(defconst *weblog-equal-sign-char* (string-to-char "="))
(defconst *weblog-at-sign-char* (string-to-char "@"))
(defconst *weblog-newline-char* (string-to-char "\n"))
(defconst *weblog-lt-char* (string-to-char "<"))
(defconst *weblog-sharp-sign-char* (string-to-char "\#"))
(defconst *weblog-char-map*
'(("\226" "--")
("\227" "--")
("\222" "'")
("\223" "\"")
("\224" "\"")
("\221" "'")
("\205" "...")
("\240" "")
("" "{tt \"")
("" "\"}")
))
(defconst *weblog-repl-map*
'(
(";-)" "<img class=\"smiley\" src=\"wink-smiley.png\"/>")
(":-)" "<img class=\"smiley\" src=\"smiley.png\"/>")
(":-(" "<img class=\"smiley\" src=\"frown-smiley.png\"/>")
("rhbz\\([0-9]+\\)" "<a href=\"http://bugzilla.redhat.com/show_bug.cgi?id=\\1\">Red Hat Bugzilla \\1</a>")
("kbz\\([0-9]+\\)" "<a href=\"http://bugzilla.kernel.org/show_bug.cgi?id=\\1\">Kernel Bugzilla \\1</a>")
("fdbz\\([0-9]+\\)" "<a href=\"https://bugs.freedesktop.org/show_bug.cgi?id=\\1\">FreeDesktop Bug \\1</a>")
("gbz\\([0-9]+\\)" "<a href=\"https://debbugs.gnu.org/cgi/bugreport.cgi?bug=\\1\">GNU Bug \\1</a>")
))
(defconst *weblog-code-map*
'(
("<" "<")
(">" ">")
))
(defvar *weblog-section* 0)
;; Append the *weblog-directory* to the given filename
(defun weblog-file (file)
(let ((res (concat *weblog-directory* file)))
(if (file-exists-p res)
res
(let ((first-two (and (>= (length file) 2) (substring file 0 2))))
(if (not first-two)
res
(let ((other-res (concat *weblog-directory* first-two "/" file)))
(if (file-exists-p other-res)
other-res
res)))))))
(defmacro weblog-while-visiting-file (buf-var file &rest body)
"Execute BODY with BUF-VAR bound to a buffer containing FILE."
(let ((exists-var (gensym)))
`(save-excursion
(let* ((,exists-var (weblog-find-or-visit ,file))
(,buf-var (current-buffer)))
(unwind-protect
(progn ,@body)
(unless ,exists-var (kill-buffer ,buf-var)))))))
(defmacro weblog-while-visiting-weblog-file (buf-var file &rest body)
"Execute BODY with BUF-VAR bound to a buffer containing (weblog-file FILE)."
`(weblog-while-visiting-file ,buf-var (weblog-file ,file) ,@body))
(defconst *weblog-init-param-descs*
'(("site-name" *weblog-site-name*)
("byline" *weblog-byline*)
("author" *weblog-author*)
("email" *weblog-email*)
("url" *weblog-url*)
("ftp-directory" *weblog-ftp-directory*)
("index-days" *weblog-index-days*
(lambda (x) (car (read-from-string x))) integerp)
("shortcuts-file" *weblog-shortcuts-file*)
("month-index" *weblog-generate-month-index-p*
(lambda (x) (equalp x "true")))
("pl-macro-text" *weblog-pl-macro-text*)
("bugmenot-auto-list" *weblog-bugmenot-auto-list*
weblog-parse-space-separated-string))
"A description for each parameter in the *weblog-init-file*
Each entry is of the form (PARAM VAR COERCE PRED), where
PARAM is the name of the parameter (before the equal sign),
VAR is the name of the variable to set to the parameter's value,
COERCE is a function of one argument to convert the value from a
string to what it needs to be, and
PRED is a predicate that is called on the coerced value. If the
value is nil, then an error is signalled.")
(defmacro weblog-with-init-params (file &rest body)
"Execute BODY with the init param variables bound to values
found in DIRECTORY or one of its parent directories."
`(weblog-funcall-with-init-params ,file (function (lambda () ,@body))))
(defun weblog-funcall-with-init-params (file thunk)
"Call THUNK with the init param variables bound to values
found in FILE's directory or one of its parent directories."
(let ((dir (weblog-seek-base-dir (file-name-directory file))))
(when (null dir)
(error "There is no %s in any directory leading to: %s"
*weblog-init-file* (file-name-directory file)))
(if (equal dir *weblog-directory*)
(funcall thunk)
(let ((*weblog-directory* dir)
*weblog-site-name*
*weblog-ftp-directory*
*weblog-index-days*
*weblog-shortcuts-file*
*weblog-generate-month-index-p*
*weblog-pl-macro-text*
*weblog-bugmenot-auto-list*
*weblog-shortcuts*)
(weblog-parse-parameter-file)
(funcall thunk)))))
(defun weblog-seek-base-dir (directory)
"Find the *weblog-init-file* in DIRECTORY or one of its parents.
Return the name of that directory or nil if not found."
(let ((file (weblog-seek-file directory *weblog-init-file*)))
(and file (file-name-directory file))))
(defun weblog-seek-file (directory file-name)
"Find FILE-NAME in DIRECTORY or one of its parents.
Return the full path or nil if not found."
(loop
(let ((file (concat directory file-name)))
(when (file-exists-p file)
(return (expand-file-name file)))
(let ((parent (file-name-directory (directory-file-name directory))))
(when (equal parent directory) (return nil))
(setq directory parent)))))
(defun weblog-file-in-base-dir (file-name)
(let ((dir (file-name-directory file-name)))
(equalp dir (weblog-seek-base-dir dir))))
(defun weblog-parse-parameter-file (&optional dir)
"Parse the *weblog-init-file* in directory DIR.
Set the variables associated with the parameters.
Return t if the file was found and all the parameters were OK.
Return nil if the file was not found.
Error if an unknown parameter is found in the file."
(when (null dir) (setq dir *weblog-directory*))
(let ((file (concat dir *weblog-init-file*)))
(when (file-exists-p file)
(weblog-while-visiting-file buf file
(weblog-parse-init-file-buffer)))))
(defun weblog-parse-init-file-buffer ()
"Parse the init file in the current buffer.
Set the variables associated with the parameters.
Return t if the file was found and all the parameters were OK.
Return nil if the file was not found.
Error if an unknown parameter is found in the file."
(goto-char (point-min))
(loop
(let* ((point (point))
(line-end (or (line-end-position) (point-max))))
(unless (eql (char-after) *weblog-sharp-sign-char*)
(let* ((equal-pos (search-forward "=" line-end t)))
(when equal-pos
(let* ((param (buffer-substring point (1- equal-pos)))
(value (buffer-substring equal-pos line-end))
(desc (assoc (downcase param) *weblog-init-param-descs*)))
(unless desc
(error "Unknown init parameter: %s" param))
(multiple-value-bind (var coerce pred) (cdr desc)
(let ((coerced-value value))
(when coerce
(setq coerced-value (funcall coerce value)))
(unless (or (null pred) (funcall pred coerced-value))
(error "Init parameter \"%s\" has illegal value: %s"
param value))
(set var coerced-value)))))))
(when (eql line-end (point-max)) (return))
(goto-char (1+ line-end))))
(weblog-get-shortcuts)
nil)
;; line-end-position doesn't exist in some versions of XEmacs
(unless (fboundp 'line-end-position)
(defun line-end-position ()
(let ((point (point)))
(end-of-line)
(prog1 (point) (goto-char point))))
)
(defvar *weblog-shortcuts-alist* nil
"Map directories to shortcuts values")
(defun weblog-get-shortcuts ()
"Set *weblog-shortcuts* from either the *weblog-shortcuts-alist*
or by loading *weblog-shortcuts=file* from *weblog-directory*."
(let ((elt (assoc *weblog-directory* *weblog-shortcuts-alist*)))
(if elt
(setq *weblog-shortcuts* (cdr elt))
(weblog-load-shortcuts))))
;; Load the *weblog-shortcuts-file* and put the result in *weblog-shortcuts*
(defun weblog-load-shortcuts ()
"Load the *weblog-shortcuts* from file in *weblog-directory*.
File defaults to *weblog-shortcuts-file*"
(let (s)
(when (file-exists-p (weblog-file *weblog-shortcuts-file*))
(weblog-while-visiting-weblog-file buf *weblog-shortcuts-file*
(goto-char (point-min))
(setq s (weblog-safe-read
(concat "Error parsing " *weblog-shortcuts-file*)
buf))))
(setq *weblog-shortcuts*
(mapcar '(lambda (x) (cons (downcase (car x)) (cdr x))) s))
(let ((elt (assoc *weblog-directory* *weblog-shortcuts-alist*)))
(if elt
(setf (cdr elt) *weblog-shortcuts*)
(push (cons *weblog-directory* *weblog-shortcuts*)
*weblog-shortcuts-alist*)))
(length *weblog-shortcuts*)))
(defun weblog-safe-read (error-string &optional stream)
"(read stream), but (error error-string) if an error happens"
(condition-case errvar (read stream) (error (error error-string))))
(defun weblog-reload-shortcuts ()
"Reload the shortcuts for the file of the current buffer"
(interactive)
(weblog-with-init-params (buffer-file-name)
(weblog-load-shortcuts)))
;; Expand a buffer
;; Replace double newlines with <p>
;; Replace {fun args...} with the result of evaluating (fun args...)
;; Replace double quoted substrings with their entry in *weblog-shortcuts*
(defun weblog-expand-buffer (&optional leave-escapes file)
"Expand the macros & shortcuts in the current buffer."
(interactive)
(weblog-with-init-params (or file (buffer-file-name))
(loop
(let ((cnt (weblog-expand-macros)))
(if (eql 0 cnt) (return))))
(weblog-narrow
"<!--BEGIN-BLOGMAX-->" "<!--END-BLOGMAX-->"
(weblog-insert-bugmenot-macros)
(weblog-add-preformatted)
(weblog-add-bullets)
(weblog-add-numbered-lists)
(weblog-add-paragraphs))
(unless leave-escapes (weblog-remove-escapes))))
(defun search-forward-non-escaped (string &optional limit)
(loop
(let ((pos (search-forward string limit t)))
(if (null pos) (return nil))
(setq pos (- pos (length string)))
(unless (eq *weblog-escape-char* (char-before pos))
(return pos)))))
(defun weblog-do-replacement (f start-delim end-delim &optional keep-delims multi-line)
"Find all one-line strings between START-DELIM and END-DELIM.
Call F on each one. If F returns non-NIL, replace the string and
the delimiters with the returned value. If KEEP-DELIMS is true,
replace only the string."
(goto-char (point-min))
(let* ((cnt 0)
(start-len (length start-delim))
(end-len (if (null end-delim) 0 (length end-delim)))
(total-len (+ start-len end-len))
pos end)
(loop
(setq pos (search-forward-non-escaped start-delim nil))
(if (null pos) (return cnt))
(if (null end-delim)
(setq end (point))
(setq end (search-forward-non-escaped
end-delim (and (not multi-line) (line-end-position)))))
(unless (null end)
(incf pos start-len)
(let* ((s (buffer-substring pos end))
(new-s (funcall f s)))
(unless (null new-s)
(unless (stringp new-s)
(setq new-s (format "%s" new-s)))
(incf cnt)
(backward-delete-char (+ total-len (- end pos)))
(unless (stringp new-s)
(setq new-s (with-output-to-string (princ new-s))))
(when keep-delims (setq new-s (concat start-delim new-s end-delim)))
(insert new-s)))))))
(defmacro weblog-narrow (start-delim end-delim &rest body)
"Apply the body within a section identified by begin-delim and end-delim"
`(save-restriction
(goto-char (point-min))
(setq begin (search-forward-non-escaped ,start-delim nil))
(unless (null begin)
(setq end (search-forward-non-escaped ,end-delim nil))
(unless (null end)
(narrow-to-region begin end)
,@body))))
(defun weblog-convert-shortcuts ()
"Convert shortcuts from double-quote delimited to {=....} delimited"
(interactive)
(weblog-with-init-params (buffer-file-name)
(if (null *weblog-shortcuts*) (weblog-load-shortcuts))
(weblog-do-replacement
'(lambda (s)
(if (weblog-lookup-shortcut s) (concat "{=" s "}")))
"\"" "\"")))
(defun weblog-directory-files (dir &optional full pattern descending year-dirs)
"Like directory-files, but matches pattern, sorted descending if true, and includes a list of sub-directories in year-dirs"
(let* ((pat (or pattern ".*\.txt"))
(files (directory-files dir full pat t))
(pred (if descending 'weblog-string-greaterp 'string-lessp))
(need-key nil))
(dolist (yd year-dirs)
(let ((yf (directory-files (concat dir yd "/") full pat t)))
(when yf
(setq files (nconc files yf)
need-key t))))
(if (and full need-key)
(sort* files pred :key 'file-name-nondirectory)
(sort files pred))))
(defun weblog-map-directory (dir f &optional pred pattern descending year-dirs)
"(funcall F) for every file name in DIR that satisifies PRED.
Consider only files that match the regexp PATTERN, which defaults
to all text files.
Opens each file in a buffer before doing (funcall F)."
(let* ((files (weblog-directory-files dir t pattern descending year-dirs)))
(dolist (file files)
(when (or (null pred) (funcall pred file))
(weblog-while-visiting-file buf file
(goto-char (point-min))
(funcall f)
(set-buffer buf)
(save-buffer))))))
;; Strangely, emacs doesn't implement string-greaterp
(defun weblog-string-greaterp (s1 s2)
(string-lessp s2 s1))
(defun weblog-map-all-files (dir f &optional pred pattern)
"(funcall F file-name) for each file in DIR and its sub-directories
that satisfies pred and matches the regexp PATTERN, which defaults to
all text files."
(dolist (file (directory-files dir t (or pattern ".*\.txt")))
(when (or (null pred) (funcall pred file))
(funcall f file)))
(dolist (file (directory-files dir t))
(when (file-directory-p file)
(let ((name (file-name-nondirectory file)))
(unless (or (equal name ".") (equal name ".."))
(weblog-map-all-files file f pred pattern))))))
(defun dir-convert-shortcuts (dir)
"This is a one-timer to convert from the old to the new shortcut syntax"
(weblog-map-directory dir 'weblog-convert-shortcuts))
(defun dir-convert-macro-shortcuts (dir)
"A one-timer to change {= to {@ in every text file in a directory"
(weblog-map-directory
dir
'(lambda ()
(replace-string "{=" "{@"))))
(defun weblog-lookup-shortcut (name)
"Look up NAME in the *weblog-shortcuts*. Return its value or nil."
(cadr (assoc (downcase name) *weblog-shortcuts*)))
(defun weblog-add-paragraphs ()
"Add <p>...</p> sections for double newlines in HTML body"
(goto-char (point-min))
(while (re-search-forward "^[^ \t<{\n]\\(.+\n\\)+?$" nil t)
(replace-match "<p>\\&</p>\n" nil nil))
(goto-char (point-min))
(while (search-forward "\n</p>" nil t)
(replace-match "</p>")))
(defun weblog-add-bullets ()
"Add <ul>...</ul> sections for lines beginning in * in HTML body"
(goto-char (point-min))
(while (re-search-forward "^\\(\\* \\(.+\n[ \t]*\\)+\n[ \t]*\\)+" nil t)
(save-restriction
(narrow-to-region (match-beginning 0) (match-end 0))
(replace-match "<ul>\n\\&</ul>\n" nil nil)
(goto-char (point-min))
(while (re-search-forward "^\\* \\(\\(.+\n[ \t]*\\)+\\)\n" nil t)
(replace-match "<li>\\1</li>\n"))
(goto-char (point-min))
(while (search-forward "\n</li>" nil t)
(replace-match "</li>")))))
(defun weblog-add-numbered-lists ()
"Add <ol>...</ol> sections for lines beginning in # in HTML body"
(goto-char (point-min))
(while (re-search-forward "^\\(\\# \\(.+\n[ \t]*\\)+\n[ \t]*\\)+" nil t)
(save-restriction
(narrow-to-region (match-beginning 0) (match-end 0))
(replace-match "<ol>\n\\&</ol>\n" nil nil)
(goto-char (point-min))
(while (re-search-forward "^\\# \\(\\(.+\n[ \t]*\\)+\\)\n" nil t)
(replace-match "<li>\\1</li>\n"))
(goto-char (point-min))
(while (search-forward "\n</li>" nil t)
(replace-match "</li>")))))
(defun weblog-add-preformatted ()
"Add <pre>...</pre> sections for text between ```"
(goto-char (point-min))
(while (re-search-forward "{{{\n*\\(\\(.*?\n*?\\)*?\\)}}}" nil t)
(replace-match "<pre>\\1</pre>" nil nil)))
(defun weblog-expand-macros ()
(weblog-do-replacement
'(lambda (s)
(if (eq 0 (length s))
nil
(cond ((eq *weblog-at-sign-char* (elt s 0))
;; {@shortcut} looks up shortcut
(weblog-lookup-shortcut (substring s 1)))
((eq *weblog-equal-sign-char* (elt s 0))
;; {=forms...} evals (forms...)
(let ((form (car (read-from-string
(concat "(" (substring s 1) ")")))))
(eval form)))
((eq (char-syntax (elt s 0)) ?w)
;; {forms...} evals (weblog-macro-forms...)
(ignore-errors
(let ((form (car (read-from-string
(concat "(weblog-macro-" s ")")))))
(eval form)))))))
"{" "}" nil t))
(defun weblog-remove-escapes ()
"Remove escape characters (\"\\\") from the current buffer"
(goto-char (point-min))
(loop
(let ((pos (search-forward *weblog-escape-string* nil t)))
(if (null pos) (return))
(backward-delete-char 1)
(forward-char))))
(defun weblog-buffer-contents ()
"Return the contents of the current buffer"
(buffer-substring (point-min) (point-max)))
(defun weblog-save-story ()
"Save the current buffer as a weblog story
using the *weblog-story-template-file*"
(interactive)
(let ((*weblog-content-template-file* *weblog-story-template-file*)
(*weblog-saving-story* t))
(weblog-save)))
(defun weblog-story-file-p (file-name)
(let* ((y (third (weblog-file-mdy file-name)))
(dir (downcase (file-name-directory file-name)))
(file (file-name-sans-extension (file-name-nondirectory file-name)))
(file-year (substring file 0 (min 2 (length file))))
(sep-char (substring dir (max 0 (1- (length dir))) (length dir))))
(or (find-if (lambda (x) (or (< x (aref "0" 0)) (> x (aref "9" 0))))
file)
(and (not (equal dir (downcase *weblog-directory*)))
(not (equal dir
(downcase
(concat *weblog-directory* file-year sep-char))))))))
(defun weblog-save (&optional template)
"Save the current buffer to an html file with the same name
using the *weblog-page-template-file*."
(interactive)
(let ((file-name (buffer-file-name))
(*weblog-content-template-file* *weblog-content-template-file*)
(*weblog-saving-story* *weblog-saving-story*))
(weblog-with-init-params file-name
(if (null file-name)
(message "No file for current buffer")
(message "%s" (concat "Saving " file-name "..."))
(when (weblog-story-file-p file-name)
(setq *weblog-content-template-file* *weblog-story-template-file*
*weblog-saving-story* t))
(let* ((content (weblog-process-replmap (weblog-buffer-contents)))
(ext (file-name-extension file-name))
(ext-len (if (null ext)
(progn (setq file-name (concat file-name ".")) 0)
(length ext)))
(html-file (concat
(substring file-name
0 (- (length file-name) ext-len))
"html")))
(weblog-save-internal file-name html-file content template))))))
(defun weblog-save-internal (file-name html-file content &optional template)
(if (null template) (setq template *weblog-page-template-file*))
(weblog-while-visiting-file buf html-file
(let ((*weblog-story-content* content)
(*weblog-story-file* file-name)
(*weblog-story-modtime* (nth 5 (file-attributes file-name))))
(erase-buffer)
(insert-file-contents (weblog-find-template template) nil nil nil t)
(weblog-expand-buffer)
(set-buffer buf)
(save-buffer))))
(defun weblog-find-template (template)
"Search for a file named TEMPLATE in the directory
of the *weblog-story-file* or one of its parents."
(weblog-seek-file (file-name-directory *weblog-story-file*) template))
(defun weblog-find-or-visit (file)
"Switch to a file's buffer.
If it existed already return true. Otherwise, return false."
(or (let ((buf (find-buffer-visiting file)))
(when buf
(set-buffer buf)
t))
(let ((buf (find-file-noselect file t)))
(set-buffer buf)
nil)))
(defun weblog-upload (&optional dont-upload-source file-name)
"Upload the current buffer to the FTP directory.
Upload only the HTML file for a text file if dont-upload-source is true.
If FILE-NAME is non-nil, upload that file and don't generate html."
(interactive)
(let ((file (or file-name (buffer-file-name))))
(weblog-with-init-params file
(let* ((buf (current-buffer))
(textp (equalp (file-name-extension file) "txt"))
(html-name (if textp
(concat (file-name-sans-extension file) ".html")
file))
(name (weblog-file-relative-name html-name *weblog-directory*))
(dont-uploadp (or (null *weblog-ftp-directory*)
(equal *weblog-ftp-directory* ""))))
(unless file-name
(if textp
(weblog-save-both)
(when (buffer-modified-p) (save-buffer))))
(unless dont-uploadp
(if (eq name file)
(message "Buffer not in *weblog-directory*")
(let ((ftp-name (concat *weblog-ftp-directory* name))
(source (if textp html-name file)))
;; Don't use copy-file here. It doesn't work on my FTP server.
(weblog-write-text-to-file (weblog-absolute-file-contents source)
ftp-name)
(when (and textp (not dont-upload-source))
(setq ftp-name (concat (file-name-sans-extension ftp-name) ".txt"))
(weblog-write-text-to-file (weblog-absolute-file-contents file)
ftp-name)))))
(when textp
(set-buffer buf)
(let ((latest-text-file (weblog-latest-text-file)))
(when (and latest-text-file
(weblog-file-in-base-dir file)
(equal (weblog-file-mdy file)
(weblog-file-mdy (weblog-file latest-text-file))))
(weblog-make-rss latest-text-file dont-uploadp))))))))
;; file-relative-name can include "\" characters in XEmacs.
;; Change them to the canonical "/".
(defun weblog-file-relative-name (filename directory)
(weblog-replace-strings (file-relative-name filename directory) "\\" "/"))
(defun weblog-write-text-to-file (text file-name)
"Write text to file-name"
(let ((buf (create-file-buffer file-name)))
(set-buffer buf)
(insert text)
(unwind-protect
(write-file file-name)
(set-buffer-modified-p nil)
(kill-buffer buf))))
;; Regular expression to match the weblog files
(defconst *weblog-file-regexp* "^[0-9][0-9][0-9][0-9][0-9][0-9]\.txt$")
(defun weblog-year-dirs (&optional dir)
(when (null dir)
(setq dir *weblog-directory*))
(mapcan '(lambda (f)
(and (file-directory-p f) (list (file-name-nondirectory f))))
(directory-files dir t "^[0-9][0-9]$")))
(defun weblog-make-index ()
"Make the index.html file with *weblog-index-days* days of data"
(interactive)
(weblog-with-init-params (buffer-file-name)
(let* ((*weblog-making-index-p* t)
(*weblog-index-files*
(nreverse (last
(delete-if '(lambda (file)
(apply 'weblog-mdy-in-future-p
(weblog-file-mdy file)))
(weblog-directory-files
*weblog-directory*
nil
*weblog-file-regexp*
nil
(weblog-year-dirs)))
*weblog-index-days*)))
(first-file (car *weblog-index-files*)))
(weblog-save-internal
first-file (weblog-file "index.html") 'generate-index))))
(defun weblog-upload-index (&optional index-only)
"Create and upload the index file.
If prefix arg is 0, generate and upload this month's files
to regenerate their calendars.
If prefix arg is 1, don't upload anything, just generate the index.
If prefix arg is 2, upload all text files in the current directory and its sub-directories"
;; Don't use 4 for index-only. This should mean to upload the index only
(interactive "p")
(weblog-with-init-params (buffer-file-name)
(weblog-make-index)
(let ((month-file (and *weblog-generate-month-index-p*
(weblog-month-index))))
(unless (eql 1 index-only)
(weblog-while-visiting-weblog-file buf "index.html"
(weblog-upload))
(when month-file
(weblog-while-visiting-weblog-file buf month-file
(weblog-upload))
(weblog-maybe-upload-previous-month-file))))
(cond ((eql 0 index-only) (weblog-upload-month))
((eql 2 index-only) (weblog-upload-directory-text))))
(shell-command "./rsync-blog &"))
(defun weblog-upload-directory-text (&optional file)
"Regenerate html for and upload every \".txt\" file
in the current directory and all of its sub-directories"
(unless file (setq file (buffer-file-name)))
(weblog-map-all-files
(file-name-directory file)
'(lambda (file)
(weblog-while-visiting-file buf file (weblog-upload)))))
(defun weblog-upload-last-month ()
"Upload last month's entries.
Useful on the first day of a month to update the calendars in last
month's entries."
(interactive)
(weblog-with-init-params (buffer-file-name)
(weblog-upload-month (nth 4 (decode-time (current-time))))))
(defun weblog-todays-mdy ()
"Return (month day year) for today"
(let* ((time (decode-time (current-time))))
(list (nth 4 time)(nth 3 time) (nth 5 time))))
(defun weblog-upload-month (&optional month year)
"Convert to HTML and upload all the weblog files for a month.
You need to do this to update the calendars to have days later than
when each day was initially generated. At the beginning of the next
month, you need to do it for the previous month to make the next month
link below the calendar point to the first day in this month.
MONTH and YEAR default to today's month and year."
(interactive)
(weblog-with-init-params (buffer-file-name)
(let* ((time (decode-time (current-time)))
(cur-day (nth 3 time))
(cur-month (nth 4 time))
(cur-year (nth 5 time))
(cur-mdy (list cur-month cur-day cur-year))
(mdy nil)
(latest-text-file (weblog-latest-text-file)))
(when (null year) (setq year cur-year))
(when (null month)
(if (and (eq year cur-year)
(equal (weblog-first-text-file-this-month)
latest-text-file))
(setq month (weblog-latest-month-before cur-month cur-year))
(setq month cur-month)))
(weblog-map-directory
*weblog-directory*
(function (lambda ()
(let ((buf (current-buffer)))
(weblog-save-both)
(set-buffer buf)
(weblog-upload (not (equal mdy cur-mdy))))))
(function (lambda (file-name)
(setq mdy (weblog-file-mdy file-name))
(or (equal (file-name-nondirectory file-name)
latest-text-file)
(and (eq month (calendar-extract-month mdy))
(eq year (calendar-extract-year mdy))
(not (apply 'weblog-mdy-in-future-p mdy))))))
*weblog-file-regexp*)
nil