From 530376ceef7d0d5e4187dbf7246bd217782d9e5a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Mon, 13 May 2013 10:04:44 +0200 Subject: [PATCH] Add Docbook documentation to tree --- configure.ac | 6 + doc/Makefile.am | 68 +- doc/gfx_and_css/logo.ai | 687 +++ doc/gfx_and_css/logo.pdf | 201 + doc/gfx_and_css/logo.png | Bin 0 -> 167215 bytes doc/gfx_and_css/netatalk.css | 251 + doc/html.xsl.in | 13 + doc/man.xsl.in | 26 + doc/manpages/man1/ad.1.xml | 402 ++ doc/manpages/man1/afpldaptest.1.xml | 97 + doc/manpages/man1/afppasswd.1.xml | 151 + doc/manpages/man1/afpstats.1.xml | 50 + doc/manpages/man1/apple_dump.1.xml | 182 + doc/manpages/man1/asip-status.pl.1.xml | 162 + doc/manpages/man1/dbd.1.xml | 124 + doc/manpages/man1/macusers.1.xml | 75 + doc/manpages/man1/megatron.1.xml | 143 + doc/manpages/man1/netatalk-config.1.xml | 163 + doc/manpages/man1/uniconv.1.xml | 227 + doc/manpages/man5/afp.conf.5.xml | 1960 +++++++ doc/manpages/man5/afp_signature.conf.5.xml | 87 + doc/manpages/man5/afp_voluuid.conf.5.xml | 88 + doc/manpages/man5/extmap.conf.5.xml | 79 + doc/manpages/man8/afpd.8.xml | 259 + doc/manpages/man8/cnid_dbd.8.xml | 270 + doc/manpages/man8/cnid_metad.8.xml | 129 + doc/manpages/man8/netatalk.8.xml | 96 + doc/manual/configuration.xml | 1554 ++++++ doc/manual/install.xml | 347 ++ doc/manual/intro.xml | 15 + doc/manual/manual.xml.in | 116 + doc/manual/upgrade.xml | 1555 ++++++ doc/netatalk.html | 14 + doc/www/ReleaseNotes | 294 + doc/www/asciidoc.conf | 610 ++ doc/www/asciidoc.py | 5902 ++++++++++++++++++++ doc/www/create-relnotes.sh | 6 + doc/www/html5.conf | 686 +++ doc/www/javascripts/asciidoc.js | 189 + doc/www/lang-en.conf | 54 + doc/www/netatalk-relnotes.conf | 285 + doc/www/stylesheets/asciidoc.css | 508 ++ macros/netatalk.m4 | 39 + man/man1/ad.1 | 6 +- 44 files changed, 18171 insertions(+), 5 deletions(-) create mode 100644 doc/gfx_and_css/logo.ai create mode 100644 doc/gfx_and_css/logo.pdf create mode 100644 doc/gfx_and_css/logo.png create mode 100644 doc/gfx_and_css/netatalk.css create mode 100644 doc/html.xsl.in create mode 100644 doc/man.xsl.in create mode 100644 doc/manpages/man1/ad.1.xml create mode 100644 doc/manpages/man1/afpldaptest.1.xml create mode 100644 doc/manpages/man1/afppasswd.1.xml create mode 100644 doc/manpages/man1/afpstats.1.xml create mode 100644 doc/manpages/man1/apple_dump.1.xml create mode 100644 doc/manpages/man1/asip-status.pl.1.xml create mode 100644 doc/manpages/man1/dbd.1.xml create mode 100644 doc/manpages/man1/macusers.1.xml create mode 100644 doc/manpages/man1/megatron.1.xml create mode 100644 doc/manpages/man1/netatalk-config.1.xml create mode 100644 doc/manpages/man1/uniconv.1.xml create mode 100644 doc/manpages/man5/afp.conf.5.xml create mode 100644 doc/manpages/man5/afp_signature.conf.5.xml create mode 100644 doc/manpages/man5/afp_voluuid.conf.5.xml create mode 100644 doc/manpages/man5/extmap.conf.5.xml create mode 100644 doc/manpages/man8/afpd.8.xml create mode 100644 doc/manpages/man8/cnid_dbd.8.xml create mode 100644 doc/manpages/man8/cnid_metad.8.xml create mode 100644 doc/manpages/man8/netatalk.8.xml create mode 100644 doc/manual/configuration.xml create mode 100644 doc/manual/install.xml create mode 100644 doc/manual/intro.xml create mode 100644 doc/manual/manual.xml.in create mode 100644 doc/manual/upgrade.xml create mode 100644 doc/netatalk.html create mode 100644 doc/www/ReleaseNotes create mode 100644 doc/www/asciidoc.conf create mode 100755 doc/www/asciidoc.py create mode 100755 doc/www/create-relnotes.sh create mode 100644 doc/www/html5.conf create mode 100644 doc/www/javascripts/asciidoc.js create mode 100644 doc/www/lang-en.conf create mode 100644 doc/www/netatalk-relnotes.conf create mode 100644 doc/www/stylesheets/asciidoc.css diff --git a/configure.ac b/configure.ac index 566df4f4..fd252e6f 100644 --- a/configure.ac +++ b/configure.ac @@ -195,6 +195,9 @@ AC_NETATALK_FHS dnl netatalk lockfile path, must come after AC_NETATALK_FHS AC_NETATALK_LOCKFILE +dnl Check for Docbook and build documentation if found +AX_CHECK_DOCBOOK + CFLAGS="-I\$(top_srcdir)/include -I\$(top_builddir)/include $CFLAGS" UAMS_PATH="${uams_path}" @@ -246,6 +249,9 @@ AC_OUTPUT([Makefile distrib/initscripts/Makefile distrib/m4/Makefile doc/Makefile + doc/html.xsl + doc/man.xsl + doc/manual/manual.xml etc/Makefile etc/afpd/Makefile etc/cnid_dbd/Makefile diff --git a/doc/Makefile.am b/doc/Makefile.am index a6fcda87..d8e2be1c 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,3 +1,67 @@ -# Makefile.am for INSTALL/ +XSLTPROC=@XSLTPROC@ +XSLTPROC_FLAGS=@XSLTPROC_FLAGS@ +XHTML_STYLESHEET=$(top_srcdir)/doc/html.xsl +MAN_STYLESHEET=$(top_srcdir)/doc/man.xsl -EXTRA_DIST = DEVELOPER +htmldir = $(prefix)/share/doc/@PACKAGE@ +dist_html_DATA = @PACKAGE@.html + +XML_MANPAGES = \ + ${top_srcdir}/doc/manpages/man1/ad.1.xml \ + ${top_srcdir}/doc/manpages/man1/afpldaptest.1.xml \ + ${top_srcdir}/doc/manpages/man1/afppasswd.1.xml \ + ${top_srcdir}/doc/manpages/man1/afpstats.1.xml \ + ${top_srcdir}/doc/manpages/man1/apple_dump.1.xml \ + ${top_srcdir}/doc/manpages/man1/asip-status.pl.1.xml \ + ${top_srcdir}/doc/manpages/man1/dbd.1.xml \ + ${top_srcdir}/doc/manpages/man1/macusers.1.xml \ + ${top_srcdir}/doc/manpages/man1/megatron.1.xml \ + ${top_srcdir}/doc/manpages/man1/netatalk-config.1.xml \ + ${top_srcdir}/doc/manpages/man1/uniconv.1.xml \ + ${top_srcdir}/doc/manpages/man5/afp_signature.conf.5.xml \ + ${top_srcdir}/doc/manpages/man5/afp_voluuid.conf.5.xml \ + ${top_srcdir}/doc/manpages/man5/afp.conf.5.xml \ + ${top_srcdir}/doc/manpages/man5/extmap.conf.5.xml \ + ${top_srcdir}/doc/manpages/man8/afpd.8.xml \ + ${top_srcdir}/doc/manpages/man8/cnid_dbd.8.xml \ + ${top_srcdir}/doc/manpages/man8/cnid_metad.8.xml \ + ${top_srcdir}/doc/manpages/man8/netatalk.8.xml + +MAN_MANPAGES = \ + ${top_builddir}/man/man1/ad.1 \ + ${top_builddir}/man/man1/afpldaptest.1 \ + ${top_builddir}/man/man1/afppasswd.1 \ + ${top_builddir}/man/man1/afpstats.1 \ + ${top_builddir}/man/man1/apple_dump.1 \ + ${top_builddir}/man/man1/asip-status.pl.1 \ + ${top_builddir}/man/man1/dbd.1 \ + ${top_builddir}/man/man1/macusers.1 \ + ${top_builddir}/man/man1/megatron.1 \ + ${top_builddir}/man/man1/netatalk-config.1 \ + ${top_builddir}/man/man1/uniconv.1 \ + ${top_builddir}/man/man5/afp_signature.conf.5 \ + ${top_builddir}/man/man5/afp_voluuid.conf.5 \ + ${top_builddir}/man/man5/afp.conf.5 \ + ${top_builddir}/man/man5/extmap.conf.5 \ + ${top_builddir}/man/man8/afpd.8 \ + ${top_builddir}/man/man8/cnid_dbd.8 \ + ${top_builddir}/man/man8/cnid_metad.8 \ + ${top_builddir}/man/man8/netatalk.8 + +if HAVE_XSLTPROC +${top_srcdir}/man/man1/%.1.in : $(MAN_STYLESHEET) ${top_srcdir}/doc/manpages/man1/%.1.xml + @xsltproc -o $(top_builddir)/man/man1/ $(MAN_STYLESHEET) $< + @$(SED) -i -e "s@:NETATALK_VERSION:@Netatalk $(VERSION)@g" $@ +${top_srcdir}/man/man5/%.8.in : $(MAN_STYLESHEET) ${top_srcdir}/doc/manpages/man5/%.5.xml + @xsltproc -o $(top_builddir)/man/man5/ $(MAN_STYLESHEET) $< + @$(SED) -i -e "s@:NETATALK_VERSION:@Netatalk $(VERSION)@g" $@ +${top_srcdir}/man/man8/%.8.in : $(MAN_STYLESHEET) ${top_srcdir}/doc/manpages/man8/%.8.xml + @xsltproc -o $(top_builddir)/man/man8/ $(MAN_STYLESHEET) $< + @$(SED) -i -e "s@:NETATALK_VERSION:@Netatalk $(VERSION)@g" $@ + +all-local: $(MAN_MANPAGES) + +endif + +distclean-local: + $(RM) -f $(top_builddir)/doc/html.xsl $(top_builddir)/doc/html.xsl $(top_builddir)/doc/manual/manual.xml diff --git a/doc/gfx_and_css/logo.ai b/doc/gfx_and_css/logo.ai new file mode 100644 index 00000000..05346569 --- /dev/null +++ b/doc/gfx_and_css/logo.ai @@ -0,0 +1,687 @@ +%PDF-1.5 %âãÏÓ +1 0 obj <>/OCGs[15 0 R 23 0 R 41 0 R 49 0 R 67 0 R 75 0 R 93 0 R 100 0 R 119 0 R 126 0 R 180 0 R 234 0 R 292 0 R 350 0 R 408 0 R]>>/Type/Catalog>> endobj 458 0 obj <>stream + + + + + application/pdf + + + Adobe Illustrator CS3 + 2009-06-03T14:56:54-07:00 + 2009-06-04T10:49:53-07:00 + 2009-06-04T10:49:53-07:00 + + + + 256 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FUm8y+cvKnle1F15h1a10uEglPrMqoz06iNCebn2UHFXjHmr/nMz8u9NLxaBY3mvTLXjJQW du3+zlDS/wDJLFXlHmH/AJzM/M2/LJpFnp+jwn7DLG1zOPm8remf+ReKvP8AV/z5/OLVSTdebNQT l1FpILMda9LYQ4qxW98z+Zb5uV7q17dMepmuJZD0p+0x7YqlmKuxVM7LzP5lsW5WWrXtqw6GG4lj PSn7LDtirKtI/Pn84tKINr5s1B+PQXcgvB1r0uRNir0Dy9/zmZ+ZtgVTV7PT9YhH22aNrac/J4m9 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 8qP+r5/063n/AFRxV8Vfnbc6LL51u10ScXGlNLLcWkgR4xwnfkF4uFYcKcdx2xVT/IbSDqv5xeU7 UDlw1CO7I36WYNyen/GHFXoH/OZnl42H5m2erotIdY0+NmfxntmaJx9Efp4q8X8sXX1fWrck0WQm Jv8AZig/4amKvRcVdiraippiqVeZ9NF3awQGUoC5c0Fa8RTx/wArFUhTybE3/H0w/wBgP64qiE8h wt/x+MP9gP8AmrFUQn5cwt/x/MP+eY/5qxVER/lfA3/SwYf88h/zViqJj/KS3f8A6WTj/nkP+asV REf5NWzf9LVx/wA8R/zXiqKsvyMtbi/trU6u6idbpi/oA0+rWU92Nuf7Rt+P01xVkXl7/nGOx1a/ 8n2reYJYR5o0FtckcWyt6DKts3ogeoOY/wBK+1t06Yqm1j/ziPp1z581XyufMsyx6bYWd8t19UUl zdyTIUKertx9DrXvirtf/wCcR9O0rzR5X0RfMs0q+Yri7gec2iqYRa2cl0CF9U8uRi49RirIv+hH NK/6m6f/AKQk/wCq2Ku/6Ec0r/qbp/8ApCT/AKrYq7/oRzSv+pun/wCkJP8Aqtirv+hHNK/6m6f/ AKQk/wCq2Ku/6Ec0r/qbp/8ApCT/AKrYq7/oRzSv+pun/wCkJP8Aqtirv+hHNK/6m6f/AKQk/wCq 2Ku/6Ec0r/qbp/8ApCT/AKrYq7/oRzSv+pun/wCkJP8Aqtirv+hHNK/6m6f/AKQk/wCq2Ku/6Ec0 r/qbp/8ApCT/AKrYqyeL/nFWwSJEPmOZiqhS31Vd6Clf73FV/wD0Kxp//UxS/wDSMv8A1UxV3/Qr Gn/9TFL/ANIy/wDVTFXyZ+alhZ6Z+YGtaTZ3RvLfTLg2S3DKELPAOEvwgtSkoYdcVeo/84Z+Xjf/ AJm3mrutYdH0+RlfwnuWWJB9MfqYq9X/AOczPKp1L8u7HX4k5TaDeD1Gp9m3vAIn/wCSqxYq+LUd kdXU0ZSCp9xir1GyukurSG4XpKgb5EjcfQcVV8VbUVOKpP5k0xbqW3Bmkj4K32DTqR/TFUtj8sxN /wAflwP9kP6Yqio/KMLf8f1yPk4/piqKj8kwt/0sbsfJx/TFUVH5Cgb/AKWd4Pk4/piqLi/Lm3b/ AKW18PlIP6Yqio/yxtm/6XGoD5SD+mKo3T/yntJtTtLc63qSCZbwl1lAYehp9zcCm37Rh4t/kk4q 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 + + + + + + uuid:1B95FEC551E111DEA13AAE68EE5F3A4D + uuid:58ad672b-c584-4acf-894c-9cb9832873ef + + + + 1 + True + False + + 3.000000 + 3.000000 + Inches + + + + Black + + + + + + Default Swatch Group + 0 + + + + + + Document + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 2 0 obj <> endobj 15 0 obj <> endobj 23 0 obj <> endobj 41 0 obj <> endobj 49 0 obj <> endobj 67 0 obj <> endobj 75 0 obj <> endobj 93 0 obj <> endobj 100 0 obj <> endobj 119 0 obj <> endobj 126 0 obj <> endobj 180 0 obj <> endobj 234 0 obj <> endobj 292 0 obj <> endobj 350 0 obj <> endobj 408 0 obj <> endobj 409 0 obj [/View/Design] endobj 410 0 obj <>>> endobj 351 0 obj [/View/Design] endobj 352 0 obj <>>> endobj 293 0 obj [/View/Design] endobj 294 0 obj <>>> endobj 235 0 obj [/View/Design] endobj 236 0 obj <>>> endobj 181 0 obj [/View/Design] endobj 182 0 obj <>>> endobj 127 0 obj [/View/Design] endobj 128 0 obj <>>> endobj 120 0 obj [/View/Design] endobj 121 0 obj <>>> endobj 101 0 obj [/View/Design] endobj 102 0 obj <>>> endobj 94 0 obj [/View/Design] endobj 95 0 obj <>>> endobj 76 0 obj [/View/Design] endobj 77 0 obj <>>> endobj 68 0 obj [/View/Design] endobj 69 0 obj <>>> endobj 50 0 obj [/View/Design] endobj 51 0 obj <>>> endobj 42 0 obj [/View/Design] endobj 43 0 obj <>>> endobj 24 0 obj [/View/Design] endobj 25 0 obj <>>> endobj 16 0 obj [/View/Design] endobj 17 0 obj <>>> endobj 407 0 obj [408 0 R] endobj 5 0 obj <>/ArtBox[16.0 18.25 200.0 202.25]/Group 453 0 R/MediaBox[0.0 0.0 216.0 216.0]/Thumb 457 0 R/TrimBox[0.0 0.0 216.0 216.0]/Resources<>/Properties<>/ExtGState<>>>/Type/Page/LastModified(D:20090604104953-07'00')>> endobj 452 0 obj <>stream +H‰”WÝnå4¾ÏSøâzl=¾¥ \­ÐŠ  bAb ꮄÄÛóÍ8qœœsŠPÕ&ãùûæϙ>ýôìž>>÷݇g·¼-ÁE*ö»êŸ¯¿.¿¸?qj?¾&v,O?þÜoߖ7GvNŽ‚8jâ%r/¯&ÿº¬Y|‹*³Šx®Í­©ùÂrÐú÷²ŒJÕ3Œìº+ÕꉛÙ__–+óª}5q?péÏËòyù´EHˆnŠJ’¯9EW¢—*mŠŒ|FÈÙLjDH̊Àˆâ“d‹‚o’ÏΛK T…pó"Ñåâc5áê3ޘ}¦ƒàoPäÙזÁ^Ð ¬)xÎÙ VvDØU çäIª+ðš-½pP\¾U䍛' ¬ìK‰F²Ó\æ¨Ò %²D¸X‹x +Ə0ªd*¢dJ¤â5ø’“¨mõA¦,ÊðAµ“Ü¥dšøÍ×Pwe!¤‹&ہ¡ ß 9æ d—Þ€7¨“h\9h~CðY@MhønɕìS5ó‘8Õè5\ %‰ˆ¥©¢ÑÙ7h!*¨ +3ßÅzPKγñ3TÌhæ´MbfAPw¨2Ùʗ4 4H«ót. í›0Cè¾M)ív$Öø=j ñ‚aøg±£Xy}±Æ 5Ï'hF.ÓAïñµ7¹öýßÓ¤„Ó¤D̺aÐìcL‚KäÑH_–‚«! Šì‰n?ˆÁBKº’A_1ITíµ˜7ÊãU7t&F/°ÀMds>PaDra6û+&˜J o9ސȈ‚0ñ² °š³+ÝcI¸EøB쁆ãõŠ9ßË." ¢Y@‘*šáJ,¢y3_É!-z|¦î%”P0°ÀHÝ­–èJÏ~oÉ![¦Üwddß6ýùRÖ®Š¸:ÌyœZƒl”>SÊ*«Ú˜÷FZëljƒljk—Fy¬…fZÙì+wOjjé¬{Ÿ“<-çèT9%íLÝMRÁ<Ãÿÿó‚ø5µ|ÔI‡»dÖ0{/Sú k +Ãa,m¥8†ø%nh*!:»xcœË‰/ièÃ{A ÃV¦-+ʦŸàWVU½‹›Cm¥ßµ íÂCÔªF5M4:DWý.ÕÞ¼l‘qp1ÐcÁm_ðE¦Ä&†OKl˜<\·¢ÛÓ祁ÑdÀJÁÇtÐh‡DòŸÿ“„íC€0Ýõ,P4{‡,¶“Ä›nm¤[ÛÓ¯Á}øk†ŽUyzzxú&ñú&ðôMâ}èÔ¡SCw  +XZ@±…˜Ô¬u¯"°l +Ï|Dß¿?ñ KíüŽ{æ v¹¶³;è™}BϨ±]~ £Þ$¢ÞøQoüG¨7ö NõÝÕØ~`ÑåiZø±±´¾ðc0cÅB^ëAÚÓVµýDëÞ°}îº+a¿¬1Ù¿½báWf> endobj 457 0 obj <>stream +8;Wj7_%"=*$tC/]^3R!p2\5ei:.#<:*2umX&QHWQSX?kZ@O:XR!2NU",:A&.IZ])d +4sFGTN1#]GN(;N>&A>I\_UN-X/[rs;\^P(tkhZ2Z7Hcmh,559!+mO$N-ilCO;X7ru +%4su8Hf\-trRT+fp'uERM?YVbeK1tUK;H$F2;_7f40XmgrX\d.MSJ41ioUrN`kD>a +XoSNdk^<4~> endstream endobj 416 0 obj <> endobj 448 0 obj <> endobj 425 0 obj <>/ColorSpace<>/ExtGState<>>>/BBox[106.997 139.145 150.43 135.867]>>stream +q +150.43 135.867 -43.433 3.278 re +W n +q +0 g +1 i +/GS0 gs +43.4326172 0 0 -43.4326172 106.9970703 137.5058594 cm +BX /Sh0 sh EX Q +Q + endstream endobj 431 0 obj <>/ColorSpace<>/ExtGState<>>>/BBox[106.997 133.854 150.43 130.576]>>stream +q +150.43 130.576 -43.433 3.278 re +W n +q +0 g +1 i +/GS0 gs +43.4326172 0 0 -43.4326172 106.9970703 132.2148438 cm +BX /Sh0 sh EX Q +Q + endstream endobj 441 0 obj <>/ColorSpace<>/ExtGState<>>>/BBox[39.6411 84.9033 88.6143 81.626]>>stream +q +39.641 84.903 48.973 -3.277 re +W n +q +0 g +1 i +/GS0 gs +-48.9736328 0 0 48.9736328 88.6142578 83.2646484 cm +BX /Sh0 sh EX Q +Q + endstream endobj 447 0 obj <>/ColorSpace<>/ExtGState<>>>/BBox[39.6411 90.1943 88.6143 86.917]>>stream +q +39.641 90.194 48.973 -3.277 re +W n +q +0 g +1 i +/GS0 gs +-48.9736328 0 0 48.9736328 88.6142578 88.5556641 cm +BX /Sh0 sh EX Q +Q + endstream endobj 444 0 obj <> endobj 418 0 obj [/DeviceN[/Black]/DeviceCMYK 419 0 R 420 0 R] endobj 419 0 obj <>stream +H‰ª6Ô3#C…¢üœbD 2óRR+2\ÉeE +©É +Å¥Iu¦ +ºèf (4*ä˛Pi3ÒS!ªåÆ0•FŒ4‚[^_ P `.§G± endstream endobj 420 0 obj <> endobj 421 0 obj <> endobj 437 0 obj <> endobj 438 0 obj <> endobj 439 0 obj <> endobj 440 0 obj <> endobj 434 0 obj <> endobj 428 0 obj <> endobj 422 0 obj <> endobj 423 0 obj <> endobj 424 0 obj <> endobj 414 0 obj <> endobj 455 0 obj [/Indexed/DeviceRGB 255 456 0 R] endobj 456 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 401 0 obj <> endobj 402 0 obj <> endobj 403 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 13.0 %%AI8_CreatorVersion: 13.0.2 %%For: (admin) () %%Title: (Netatalk_Logo.ai) %%CreationDate: 6/4/09 10:49 AM %%BoundingBox: 1168 738 1352 923 %%HiResBoundingBox: 1168 738.25 1352 922.25 %%DocumentProcessColors: Black %AI5_FileFormat 9.0 %AI12_BuildNumber: 434 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%CMYKProcessColor: 1 1 1 1 ([Registration]) %AI3_TemplateBox: 1260.5 827.5 1260.5 827.5 %AI3_TileBox: 972 472 1548 1206 %AI3_DocumentPreview: None %AI5_ArtSize: 216 216 %AI5_RulerUnits: 0 %AI9_ColorModel: 2 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: 1107 936 4.86 1509 1080 26 1 0 50 75 0 0 1 0 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:0 0 %AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 1 %AI12_CMSettings: 00.6 %%EndComments endstream endobj 404 0 obj <>stream +%%BoundingBox: 1168 738 1352 923 %%HiResBoundingBox: 1168 738.25 1352 922.25 %AI7_Thumbnail: 128 128 8 %%BeginData: 16714 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FD35FFA87D52522727FD09F8272752527D7DA8A8FD64FFA87D2727 %FD19F8527DA8A8FD5CFF7D52FD21F827277DA8FD56FF7D52FD28F8277DA8 %FD50FFA852FD2DF82752A8FD4CFF7D27FD14F827F827F8272727F827F827 %FD13F8277DFD48FF7D27FD0FF8FD04275227522752275227522752275227 %5227522727F827FD0FF87DFD44FF7DFD0FF8FD042752FD132752FD0627FD %0DF8277DFD40FFA827FD0BF8272752275227522752275227522752275227 %522752275227522752275227522752275227522727F827FD0AF827A8FD3D %FF52FD0CF827275227272752272727522727275227272752272727522727 %2752272727522727275227272752FD0427FD0BF852A8FD39FF7DFD0BF827 %275227522752275227522752275227522752275227522752275227522752 %275227522752275227522752275227522727FD09F8277DFD36FFA852FD0A %F82752FD2F27522727FD0AF827FD34FF7D27FD09F8272752275227522752 %275227522752275227522752275227522752275227522752275227522752 %275227522752275227522752275227522727FD09F8A8FD31FF52FD0AF852 %272727522727275227272752272727522727275227272752272727522727 %27522727275227272752272727522727275227272752272727522727FD09 %F87DFD2FFF27FD08F8272752275227522752275227522752275227522752 %275227522752275227522752275227522752275227522752275227522752 %275227522752275227522752FD09F852FD2DFF27FD08F8272752FD3E27FD %09F827A8FD2AFFFD09F85227522752275227522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %52275227522752275227522752275227522727FD07F827A8FD27FFA8FD09 %F85227272752272727522727275227272752272727522727275227272752 %272727522727275227272752272727522727275227272752272727522727 %275227272752272727522727FD08F8A8FD25FFA8FD07F827275227522752 %275227272752275227522752275227522752275227522752275227522752 %275227522752275227522752275227522752275227522752275227522752 %2752275227522727FD08F8A8FD23FFA8FD08F82752FD0727F8F8FD4127FD %08F8A8FD22FFFD07F82727522752275227522727F8272752275227522752 %275227522752275227522752275227522752275227522752275227522752 %27522752275227522752275227522752272727522752275227522752FD07 %F827A8FD20FFFD07F8FD0427522727275227F8F8F8272727522727275227 %272752272727522727275227272752272727522727275227272752272727 %5227272752272727522727275227272752F8F8F8522727275227272752FD %07F827A8FD1EFF27FD06F82727522752275227522727F8F8F85227522752 %275227522752275227522752275227522752275227522752275227522752 %27522752275227522752275227522752275227522727F8F8F85227522752 %27522752FD07F827FD1DFF27FD06F8FD08275227FD05F8FD3927FD04F8FD %082752FD07F852FD1BFF7DFD06F82727522752275227522727FD05F85227 %522752275227522752275227522752275227522752275227522752275227 %52275227522752275227522752275227522752275227522752FD05F82727 %52275227522752FD07F87DFD19FF7DFD07F8275227272752FD0427FD05F8 %272752272727522727275227272752272727522727275227272752272727 %52272727522727275227272752272727522727275227272752FD0427FD05 %F8272752272727522727FD07F8A8FD18FF27FD05F8272752275227522752 %2727FD06F827522752275227522752275227522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %522752FD07F8272752275227522727FD07F8FD17FF52FD06F82752FD0827 %FD07F8FD3B27FD07F8FD0927FD06F827FD15FFA8FD06F827522752275227 %522752FD07F8272752275227522752275227522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %522752275227FD08F8522752275227522727FD06F87DFD14FFFD07F82727 %52272727522727FD09F85227272752272727522727275227272752272727 %522727275227272752272727522727275227272752272727522727275227 %272752272727522727FD09F85227272752FD0427FD06F8A8FD12FF52FD06 %F852275227522752275227FD08F827275227522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %5227522752275227522752275227FD0AF8522752275227522727FD05F852 %FD11FF7DFD06F8FD0B27FD09F8FD3B27FD0BF852FD0627FD07F8A8FD10FF %27FD05F82727522752275227522727FD09F8272752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %522752275227522752275227522752275227FD0AF8272752275227522752 %27FD05F827FD0FFF7DFD06F827272752272727522727FD0BF82727522727 %275227272752272727522727275227272752272727522727275227272752 %2727275227272752272727522727275227272752FD0427FD0BF8FD042752 %FD0427FD06F87DFD0EFF27FD05F82752275227522752275227FD0AF82727 %522752275227522752275227522752275227522752275227522752275227 %52275227522752275227522752275227522752275227522752275227FD0C %F8522752275227522727FD05F827FD0DFF52FD06F8FD0B27FD0BF852FD3A %27FD0DF852FD0727FD06F87DFD0CFF27FD05F82727522752275227522752 %FD0BF8272752275227522752275227522752275227522752275227522752 %275227522752275227522752275227522752275227522752275227522752 %275227FD0CF827275227522752275227FD05F827FD0BFF7DFD05F8FD0427 %52272727522727FD0DF85227272752272727522727275227272752272727 %522727275227272752272727522727275227272752272727522727275227 %272752272727522727FD0DF8FD04275227272752FD06F87DFD0AFF27FD05 %F8275227522752275227522727FD0BF82727522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %52275227522752275227522752275227FD0EF8522752275227522727FD05 %F827FD09FF7DFD06F8FD0B27FD0DF8FD3A27FD0FF8FD0A27FD05F8A8FD08 %FF52FD05F8272752275227522752275227FD0CF827275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %52275227522752275227522752275227522752FD0FF85227522752275227 %5227FD05F852FD08FFFD06F82727275227272752FD0427FD0DF827275227 %272752272727522727275227272752272727522727275227272752272727 %5227272752272727522727275227272752272727522727FD10F827522727 %275227272752FD06F8A8FD06FF7DFD06F85227272752272727522752FD0E %F8275227522727275227272752272727522727275227522752FD0B275227 %522752275227522752275227522752275227522727FD0FF8272752275227 %5227522727FD05F87DFD06FF52FD05F8FD05522752525227525227F827F8 %27F827F827F827F827F827525227525252275252522752525227FD055227 %522727FD07F827F827F8FD04275227272752FD0C27FD11F8FD0A27FD05F8 %27FD06FFFD05F852FD29FFA87DFFFFFF7D7DFFFF27FF52A8527D527DFD0E %52275227522752FD0427FD11F82727522752275227522752FD05F827A8FD %04FF7DFD05F87DFD29FF7D7DFFFFFF7D52FFFF27FF7D7D52527D527DFD06 %5227522752275252522752FD0627FD13F827275227272752FD0427FD05F8 %7DFD04FF52FD05F8FD0D52FD1027FD0C522727F8272727F8272727F827F8 %27F827F827F827F827F827F827F827F8F8F827275227522752275227FD12 %F8272752275227522752275227FD05F852FD04FFFD05F827FD047D527D7D %7D52FD057DFD0F527D7D7D527D7D7D527D7D7D5252272752525227F87D52 %2752FD0727F827F827F827F827F827F827F8FD0927FD13F852FD0A27FD05 %F827FFFFFFA8FD05F87DFD2AFFA852FFFFFF7D7DFFFF27FF7DA8527D7D7D %527D527DFD0652275227522727275227522727F827F827FD0FF827275227 %522752275227522727FD05F8A8FFFF7DFD05F87DA8FFA8A8A8FFA8A8A8FF %A8A8A8FFFD13A8FFA8FFFD05A8FF5252A8A8A85227FFA827A8527DFD0552 %275227522752FD0827F827F8272727F827FD12F827522727275227272752 %272727FD05F87DFFFF52FD04F827275227522752275227522752275227FD %12F827275227FD2AF827FD15F827275227522752275227522752FD05F852 %FFFF27FD05F8FD0F27FD13F827FD42F8FD0D27FD06F8FFFFFD05F8272752 %2752275227522752275227522727F8F8F827F8F8F827FD07F827FD07F827 %FD08F8527D527D527D527D27FD06F827F8F8F827F8F8F827FD07F827F8F8 %F827FD07F827FD08F82752275227522752275227522752FD05F827FFA8FD %06F852277DA8FFA8FF522727A8A8FFA8FF27F8F8FD08A8FF27F8FD0BA8FF %27FD06F8FD08FF52F8F8F8FD0CA852F8F827FFFD07A827F8F8F827FD05A8 %7DFD05F8FD04A8FF7D2727FD04A8FF7D27FD05F8A87DFD05F82727277DFD %04FFA82752A8FD04FF52F827FD09FF52F8FD0CFF52FD05F87DFD08FF27F8 %F827FD0CFF52F8F852FD08FF27F8F8F827FD05FF7DFD04F827FD06FF2752 %FD05FF7D27FD05F87D52FD05F82727F87DFD05FF5227A8FD04FF52F8F8FD %09FF27F8A8FD0BFF27FD05F87DFD08FF27F8F8F8FD0CFF7DF8F852FD08FF %52F8F8F827A8FD04FF7DFD05F8FD05FF7D277DFD05FF2727FD05F85252FD %05F85227277DFD05FF7D27A8FD04FF522727FD09FF52F8FD0CFF7DFD04F8 %27FD09FF27F8F827FD0CFF7DF8F8A8FD08FF7DF8F8F827FD05FF7DFD04F8 %27FD05FFA827FD05FF7D272727FD04F85227FD04F8FD04277DFD05FFA827 %A8FD04FF5227F8FD05FFA852527D27F8527D52A8FD05FF7D527D27FD04F8 %52FD09FF27F8F8F852527DA8FD05FF7D7D5227F8F8A8FD08FFA8F8F8F827 %FD05FF7DFD04F827FD05FF7D52FD05FF522752FD05F82727FD05F8522727 %7DFD06FF52A8FD04FF522727A8FD04FF7DFD08F87DFD05FFFD08F87DFD09 %FF27FD06F87DFD05FF27FD05F8FD09FFA8F8F8F827FD05FF7DFD04F852FD %05FFA87DFD05FF27522727FD04F827FD05F8FD04277DFD06FF52A8FD04FF %272727FD05FF7DFD08F8A8FD05FF27FD07F8FD05FF7DFFFFFFA827FD06F8 %7DFD05FFFD05F827A8FFFFFFA8FD05FFF8F8F827FD05FF7DF8F8F82727FD %05FFA8A8FD04FF7D272727FD05F827FD06F85227277DFD06FFA8A8FD04FF %522752FD05FF7DFD08F8A8FD05FF27FD06F852FD04FFA8A8FD04FF27FD06 %F8A8FD05FF27FD04F827FD05FFA8FD04FF52F8F827FD05FF7DF8F8272752 %FD0BFF7D27522727FD0AF8272752277DFD06FFA8A8FD04FF522727FD05FF %A87D527DFD05F8A8FD05FF27FD06F87DFD04FF7D7DFD04FF27FD06F87DFD %05FF27FD04F852FD04FF7D7DFD04FF52F8F827FD05FF7DF8F8272727FD0A %FFA8FD0427FD0BF8275227277DFD0CFF522752FD09FF27FD04F87DFD05FF %27FD06F8FD05FF27A8FD04FFFD07F87DFD05FF27FD04F852FD04FF7D52FD %04FF7DF8F827FD05FF7DF8F8522752FD0AFF7D2727522727FD0AF8272727 %F87DFD0CFF522727FD09FFFD05F8A8FD04FFA827FD05F827FD04FFA8F8A8 %FFFFFFA8FD07F87DFD05FFFD05F87DFD04FF5252FD04FF7DF8F827A8FD04 %FF7DF8FD0427FD0AFF7D27272752FD0BF8275227277DFD0CFF522752FD09 %FF27FD04F87DFD05FF27FD05F87DFD04FF7DF8FD05FFFD07F87DFD05FF27 %FD04F8A8FD04FF5227FD05FFF8F827FD05FF7DF827522752FD0AFFA85227 %522727FD0AF8FD04277DFD0CFF522727FD05FFA8A87DA8FD05F8A8FD05FF %27FD05F8A8FD04FF2727FD04FFA8FD07F87DFD05FFFD05F8FD05FF2727FD %04FFA827F827FD05FF7DF8FD0427FD0BFF5227275227FD0AF8275227277D %FD0CFF522752FD05FFA8FD08F8A8FD05FF27FD04F827FD05FFA8A8FD05FF %FD07F87DFD05FF27F8F8F827FD05FF5227FD05FF27F827FD05FF7DF8F827 %2752FD0BFF7D27522727FD0AF8FD04277DFD04FF7DFD07FF272727FD05FF %7DFD08F8A8FD05FF27FD04F852FD0BFFA8FD07F87DFD05FFFD04F827FD05 %FFA8FD06FF52F827FD05FF7DF8F8F82727FD05FFA8FD05FFA8272727FD06 %F827FD05F85227277DFD04FF7DA8FD06FF522752FD05FFA8FD08F8A8FD05 %FF27FD04F8A8FD0CFFFD07F8A8FD05FF27F8F8F852FD0CFF52F827FD05FF %7DFD04F852FD05FFA8A8FD05FF52522727FD05F827FD04F8272752277DFD %04FF52A8FD06FF522727FD05FF7DFD08F8A8FD05FF27F8F8F827FD0CFFA8 %FD07F87DFD05FF27F8F8F852FD0CFF7DF827FD05FF7DFD04F827FD05FF7D %7DFD05FF7D2727FD05F82752FD05F85227277DFD04FF5252FD06FF522727 %FD06FF527D7D52FD04F87DFD05FF27F8F8F852FD05FF7D27A8FD04FFA8FD %07F87DFD05FF27F8F8F87DFD0CFFA8F8F8FD05FFA87D527DF827FD05FFA8 %52FD05FF7D272727FD04F82727FD05F82727F87DFD04FF5227A8FD05FF52 %27F8FD09FFA8FD04F8A8FD04FFA827F8F8F8A8FD05FF27F8A8FD04FFA8FD %07F87DFD05FFFD04F87DFD05FF5227A8FD05FFF827A8FFFFFFA8FD04FFF8 %F8FD05FF7D52A8FD05FF2727FD05F8527DFD05F85227277DFD04FF7D27A8 %FD05FF52F827FD09FFA8FD04F87DFD05FF27F8F827FD06FFF827A8FD04FF %A8FD07F87DFD05FF27F8F8F8FD06FF27F8A8FD05FF27F8FD09FFF8F8FD05 %FFA827A8FD05FF7D27FD05F8527DFD05F82727277DFD04FF522752FD05FF %52F8F8FD09FF7DFD04F8A8FD05FF27F8F827FFA8FFA8FF7DF8F8FFFFFFA8 %FF7DFD07F87DFD05FFFD04F8A8FD05FFF8F87DFD05FF2727FD08FFA8F8F8 %FD05FFA8277DFD05FF7D27FD05F87DA8FD05F82727277DFD04FF7D2752FD %05FF52F8F8A8FD08FFA8FD04F87DFD05FF27FD04F827F827FD05F827F827 %F827FD07F87DFD05FF27F8F827FD05FFA8F8F87DFD05FF52F8FD09FFF827 %A8FD04FFA8277DFD05FFA827FD05F8A8A8FD06F852272727522752FD0527 %52275227FD55F82752275227272752275227522727FD05F8FFFF27FD04F8 %2727522752275227522752275227522727FD55F827275227272752272727 %522727FD05F827FFFF52FD05F8FD04275227272752272727522727FD0DF8 %27F8F8F827F8F8F827F8F8F827F8F8F827F827F82727F8F8272727F82727 %27F8272727F8272727F8272727F8272727F8272727F8272727F8272727F8 %272727F8272727F8272727F82727FD0C52FD05F827FFFF7DFD05F8275227 %5227522752275227522752FD07275227522752275227FD08527D527D527D %5252527DA852FFFF52A8FFFFFF52A8FD38FFA8FD05F87DFFFFA8FD05F8FD %0E2752FD0B2752275227FD0C527D527D52FF52FFFF52A8FFFFFF27FD39FF %52FD05F8A8FFFFFF27FD05F8522752275227522752275227522727F827F8 %27F827F827F827F827F827F827F827F827F827F827F827F827F8FD0527F8 %FD3227FD04527D5252527D52525227FD05F8FD04FF27FD05F82752272727 %5227272752FD0427F8F8F827F8F8F827F827F827F827F827F827F827F827 %F827F827F827F827F852F852272727522752F82727522752275227522752 %275227522752275227522752275227522752275227522752275227522752 %27522752527D527D527D527D527D5252FD05F852FD04FF7DFD05F8272752 %27522752275227522752FD05275227522752275227FD08527D527D527D52 %FD057DA87DFFFF52A8FFFFFF52FD38FF7DFD05F87DFD04FFA8FD06F8FD0A %27522727F8FD0C275227522752275227FD08527D52A827FFFF527DFFA8FF %27A8FFFFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFFF52FD05 %F8A8FD05FF27FD05F827522752275227522752275227FD04F827F8F8F827 %F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F8F8F827F827F827F827 %F827F827F827F827F827F827F827F827F827F827F827F827F827F827F827 %F827F827F827F827F827F827F827F827F82727522752275227522752FD05 %F852FD06FF7DFD05F827275227272752272727522727FD57F8FD0927FD06 %F87DFD06FFA827FD05F8522752275227522752275227FD56F82727522752 %2752275227FD06F8FD08FF27FD05F8FD0C27FD56F82752FD0827FD05F852 %FD08FFA8FD05F827275227522752275227522727FD55F852275227522752 %2752FD06F8A8FD09FF27FD05F82727522727275227272752FD56F8275227 %272752272727FD05F827FD0AFF7DFD06F85227522752275227522727FD55 %F8522752275227522727FD05F8A8FD0BFFFD06F8FD0727522727FD55F8FD %0927FD05F827A8FD0BFF7DFD05F82727522752275227FD58F82727522752 %2752FD06F87DFD0DFF27FD05F82727522727FD5DF8272727FD07F8FD0EFF %7DFD06F8522752FD67F87DFD0FFF27FD06F827FD67F827FD10FFA8FD6EF8 %A8FD11FF52FD6CF852FD12FFA827FD6BF8FD14FF7DFD6AF87DFD15FF52FD %68F852FD17FFFD67F827A8FD17FFA8FD66F87DFD19FF7DFD64F852FD1BFF %52FD62F852FD1DFF27FD60F827FD1FFF27FD07F852275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %5227522752275227522727FD07F8FD20FFA8FD08F8FD4F27FD07F8A8FD21 %FFA827FD07F8522752275227522752275227522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %5227522752275227522752275227522752275227522727FD07F8FD24FF7D %FD08F8FD0427522727275227272752272727522727275227272752272727 %522727275227272752272727522727275227272752272727522727275227 %27275227272752272727522727275227FD08F8A8FD25FFA827FD07F82727 %522752275227522752275227522752275227522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %52275227522752275227FD08F8FD28FFA827FD07F8FD4527FD09F8A8FD2A %FF27FD07F827275227522752275227522752275227522752275227522752 %275227522752275227522752275227522752275227522752275227522752 %275227522752275227522727FD08F827FD2DFF52FD09F8FD042752272727 %522727275227272752272727522727275227272752272727522727275227 %27275227272752272727522727275227272752272727522727FD08F827FD %2FFF7DFD09F8272752275227522752275227522752275227522752275227 %522752275227522752275227522752275227522752275227522752275227 %52275227522727FD08F87DFD31FFA8FD0AF8FD3827FD0AF87DFD34FF52FD %0AF827275227522752275227522752275227522752275227522752275227 %52275227522752275227522752275227522752275227522727FD09F852FD %37FF7DFD0AF8FD0427522727275227272752272727522727275227272752 %2727275227272752272727522727275227272752FD0427FD0BF87DFD3AFF %52FD0BF82727522752275227522752275227522752275227522752275227 %522752275227522752275227522752275227FD0BF852FD3DFFA827FD0CF8 %FD042752FD2027FD0DF8277DFD40FF7D27FD0DF827275227522752275227 %52275227522752275227522752275227522752FD0427FD0CF8277DFD44FF %7DFD11F8FD042752272727522727275227522752FD0627FD0FF82752FD48 %FF7D27FD11F827F8FD0B27F827FD12F827A8FD4CFFA827FD2EF8527DFD50 %FFA87D2727FD27F8527DFD56FFA87D2727FD21F8527DFD5DFFA87D522727 %FD17F827527DA8FD64FFA8A87D522727FD0BF8272752527D7DFD34FFFF %%EndData endstream endobj 405 0 obj <>stream +%AI12_CompressedDataxœì}i_úÈÒèý|P‘’‚Š   ¢¸áΦ"«fÎÿyñ|ö[ÝÙCÂrîœçÞû›G;ªêêªêªê®´ßWoÄòÝI»KÆ ¯Çï/ˆ½Ö|"f¼¸Õ{6.fs5oB^zA§üYú]îxßgýÉ8ƒÅ)x( ·ƒ­î¨?yƒ!h¹íχ=h»ìÍ[óÖpð^›|Mâ­~HÁŠ­9tat‚à¼$‘¡9oþžó“ŸÛñ“’I{Ùd¥(/G%¡C¥ӛYöŠS)¥#¿Cß⤳õÆóº8éôf³Âd8g/?luUê]è{0€QkîåÐPóg$õÎ/úÃîåbÔîÁÈè$š“ïøí»Yë èÆ¿£föýl-Þ|älÄ®ÂE³ªG $ÊÿŸoz_}Ì`àÁkH‚|ÛM‡Ài4CÄSÞ4ÅÂOýr_ ÷ãXÊKÃdŠþP#=׆Üû«ßû;㽜Œ{ÒXóâ¼Ñÿ/ ž"ôŸÔz³öÄ»q.Ÿ?㤑^Lº½!ôU߆-<@ü©ý”:ܶįÞ¦f2\̱€¤ ùp²ÖúÓCœ'%WÓÞøvré#I‚õrIÆKÇÓ Œ‹CšðR Ÿ"¼lJE†ÿ“ "‚šÆ×a*®ÄþWœ‘ cßËb¿«M0,-ýÀ´ÇÓºÿ8å?‰Hï|ÞËDƒX.tÓLāþÒ¸[˜Œ¿gHvanÇ0ñÃɗôLý?·S‰xü÷;LM]ìHÏ%~’~¯ð¨,NÓ³ñçÄ”ô²ÞšƒÜöÆÝ(™Ô&ýé•Þ€ÖZÿ¯žÔº6 9»[@ë½jÿô:sxYnÐ~k,úóÞj@â’èåÅÅìÛ{;™ UúŒT2åf܊úÿgà¨ãÆWc‰Ó˘äfL ÿqX ·=xøŸ ½Ðû_bkúÝïX!°x®b’ž­ T\ìiïã?•ÿ»Ë?£öd؟4iÔµÔ[â¼ßöfóÞh5´bïÖ!ÛpkiüWo8™êˆT[Zã®÷¡%N@£iú쏻 !XŸ56NFS´†zß­i“;ÿpφ 1¶Æ-ыÛUÈü€ü‚=3š$©Mšzè7}±˜‡òòca,‹­nl*¸wãqkÔëz¿ä&XéBËV0ç¦ëyö{A( E¡ ðB^à„´À +Œh!)P)%¡T*K…_ʗ¸RºÄ–˜RªD—’%ªD–ˆ¢P,‹ÅB‘/æ‹\1í)²E¦˜*ÒÅd‘*’E¢ J…b¡Pà ùWHØSHèB²@ÈÁ |‰/òžçó<ǧy–gøOóIžâIžðä…|)_Ìò|>Ÿçòé<›gò©<Oæ©<™'8+qE®Àñ\žã¸4Çr —âh.ÉQÉi!]JӅ4ŸÎ§¹tړfá&JÓédšJ“i‚Ø[d ,ÏæY³,æXšM²K²#0%¦ÈžÉ3“fX†aR Í$Š!= ‘R¥T1UHñ©|ŠK¥SlŠI¥Rt +|¬,Ô´@—è"] y:Ostšfi†NÑ4xLIš¢IšH +ÉR²˜,$ùdޓä’é$›d’)xžLRI2IPU¢ŠTâ©<ÅQàçP •¢h*IQ85¤@Â|E²@òdžäÈ4ɒ ™"i2é!)œ¦“€!xØž ÿñ 'x!Yáõ¿ó"È!¹ЊZÿ»®…ŸyW@sq:E0^µ‹±zñEU’Yu)¾´•ôÒÿ/ /ˆ¯'þ÷ +ïzâK&N1)ID8&Îɤ*8ËOv$@Ž¡•Õt|“ž Ÿþg؛yÕñäï1þÛñŸa½i-†óא7q âèz>Ä!=¥ á½ò²»?€ßn<Šë_õ(á}üœÃ/?Ðô·—ö^xŸ_ oZo<(æyìzÒ²päñ&-ü#ÒÈ[9ÆzkÞwQo{ì"*DÒ̳gÕ;ž:¯"Q`­ÃÁ€Ó³mi ¸Ã&SEÒ#É{@`þ—Ü €L6ÀeZ•à‚ëˆ]BùfýÕï n´Ä?ÒߏµKò,yƒÿ Çð8©Øo/æ=ˆ<¢¨k^[ÿg@ì¾®WçÂz±7–úPÞÄpF}Š~Ìÿ _= ŽgïµÄÙèG C¬dèúWk¸Pú¢ö™M?dê¥n2%3Ã_ÿC¹Óîãô é‚9ÀD|/1VsHß;úώq<ۑ¬ßpÒôºnƦôÜÑôo9zÒqô®æµßj{nåLþORôÌ_®Uuý‡¥ ¯³˜Í'£֒ýûä03k!·­y`:܊ã¿]/€–ÿ RþoÐÒÙçßÿÁ«ñ?¬³a¿ó?ÝÇ. |¸éµ†Óëjjÿi»+oG­Ë7cùóO…d’é䪡üÝïÎ¿Ý Gîø)M¯Ðw¯ÿõíƄ«=ÿá!yüdâ0 öd^@­÷9—¶ÈÜ mùÿ€›ºÆd!vzxø_òaaú§Iõæ­.x@ÛÒÁmIÇ~WN9¸‘.]gƒ£Ü‡÷SÚ~í‰ÞÉb>ì{ÞÙ\œ AcS2¥¦Îbk6¿zùDô¶[ÃÖ¸cÖÓ+aêíLù/¯Øû•‘+(š÷þµ‚ö©Ø›õÄ¿zÞÉ_=qŠr6 I¯Âo6A'¼m´‹÷c1ô¡˜«üç½è;½7x ýÿÂ.¯¶ñî~0†¨÷~I[7¨+J·§Ufrޖ8oOZbX2RÀ9hM2®ó)€r&„I¥’){è$PÒë¹íÛVmeW•hJ×k.¶Æ³i ¤½ó÷»ÞYÿ¿z.Ù !_ÙU7&Ò0uEnAr¼¥nÞj÷‡ýùuÖ™&½jòŸðÖZã¯Eë«ç­O¦ÊÔŸ/zÝþb¤ã:ÊçލûÐ5Eÿ¬  ª„b’æ^]ÊÅ´j%eÒ§­n×$^£Öl`èl:™›zµ†}Em•éèNûq™›r 0OTMCþ̛_Ì'ªpõ–ÄšóN[S Ö-†-MàHۉ1òx?«·D5ÀŸ•Fí:­ñÙöª½?¦º¢S`ÙLC¨i…¡ccÑÆ +PùDß +¸õ¢€@CO+‰0t}øîw¾ÍÔ!":±Ûë.O£7q9™“zNŽ'š}òöÇØNfx¿ØAÌtòEyaÂdJ¯¸oÙ܂lsoô6WoØqWI÷’Eç ]o¥ÞÊv +Þ— ¥É"¬˜ôR stt½„UÓü’QKõ†Ã–û^ÉëEC¿‚­æªô²[õ¼’:»f–Ô}]nIo­f× +á3ì>™¬UBs) ¶Ãd%5ôý™w1 3ÜqgñÓMZGìOÝ~¥3pkЭÎ7qš3U\HšäÛßêö^V¿„KPy5Ð¥RŒ“‰­?¨.¶¡ß'°*2êü¦"\Ól–¡˜ÒYK÷ãl¿½³¤ Ñ6ÛoÕýVì.P`ºZ]óÖ¸ÛÝåL”—äó.€•¡«Êë‚jçyÅBàjbe;ŠS¹ˆI·ÚÉ1×Ê¢X8wF[ÙʋÁ²^@‹ ¹ÒIÅj¼ÞÔH­…ɸÛ_©Í'`e>ÓO6é,³d=èÂAç¡Ã´X lj$/ô®ÒfÝ&›މ&IUûŠ“a5Q:_LƓη8õ´Q˜w0,½|÷g ,“KªñÄYnéù{"t6&e£@®výŒžµNÝÌLë8m™9ÒsKãþÜA¾=ùËYGܝ¯X©üÖràH®QזŽ#¬5/:_Š¢lÂJ%\2I§ÜðXúNZë‘ÒϏ3nã€]¢68žùŽ8i·æҗ6°LKÄoš\x.myj0כ¿Uî'%qÓ}6î€[gÒ Òmy:i3Ã~Ö6ö?ûJ´jIÑiÇõ¢¥¹6ù×gvœðZÙNw&ڬВiW(-ÍÁz"¡÷>œ ‰I§ÍL‚ÀzIòYP#>9¨äûJ@™oÎÎÒ©bÉzHŸ^>GŽNG­Çèyòð*ÆçÄòè;ó5ö ¾h0Pè·â³æ®Rbö3¹»rö‚>ÍÔ^9qÑa…u‘ö“4½O³âOñ+JäŽÞâáÜqt:ËͪTÂãÏÕ|¢Òé|ÎU®k¹cº×(ôO²b<øZBUë6[üö©@[ŠŸ¯,?Ì 3Ÿü÷¼ðÍ>‘v|;dí7w|xàɳÂÛ×Û~;ü-žuÏ||,ýsoÄöÇ ­îÂãç~‚‘N©“º¾é÷£ã¼?yá/£‘\!p'z‹pöþ|ÿû¨Ói ÐoýHé³ö-a&‰D‹û™þÛy—úO11ò²Èׇ¿ˆþPîèü;éñ3G÷¯¹ü¸EN.Žìèå¤Ï²‰Ùg2/vÎÈÈ Cª;ÅóÙ=° ô؇$ÑÍô ‰Ì/yqŒE{ü­¤4kþ\áìxÿ¡åR3˜—³gf?Ë&o‘ãûîs†jï¿b°Ù±”eÂûhJž™æzŒø”å!&&‹æ}·F¯ûÅDëøPðEžD„…AÞ0ÜÅã'Ú{g4þ=’ŽåߎJU©{!Zú€QMê D÷‘ˆd³¥(U<ý:‘á<œu.ßðLª¼+>%cNü¹JÀ«F<¹Az4nKùøâ;f5øò§4óÄütò·ÅŸHñ3Qý-µZžiß]suÿã]þªÀ׋Ÿþoî÷5óåñótóö]bæÓ}*½“‘{ž~Ì_ ş‡÷Bÿ‡I}Žü_Bá3L³,{ӝhøҍßQ5U W…b¨[•x£0Z’}˜ýù4~9½oýJÊ2éVîèv¾—¿=Ÿ/–‡f⬎ÊD<Š>T4çª0÷øKO]ÿõq|Z$„—\‹ÀñDZPéG#ü„{3ϕ‘³ú‰U&B’œÓïÙs Æ¢çÓy­œ?'±ÄG¦GBð³ÏÇ·OTèàíX"ÄÈfqÃõ„àá4Tøfn¥H-.h’ + +М Ó(u‘„æ@©F‡0´ýPáë»4c:w7yö‰z0ÏA½2¼7ÀÞ+—bÑ6g5%Ü W-xüùۋn,L–+òµæÀŠZÜSׯÜd?AiJA•é‹eəׅP}x$SOT$[þˆyüÚ¸`TO¡TL±<»ºÇ'NVî£i1ñ1 ó?óï'ùÛïÇQ¨¦¥èä’cìsþæó;¯½òL¤ö-YËPñ3Pa€ÚÉ·dK÷ń‚êëmRÈ}<"ÿQlÓßù;gfìw˜¿¹}¦†±clÑ´…°hÏ'çÄ1?NûÂq™ êlûÓM¸¥ç ¬%:cMíE)xòû ­4¦§ ÉÁ9ûäõKÏÓ‘£³Ù'XìÎ~Š'Ãç|ãã¼ ?åò¹ãJ!]>îÁ +Ôyòiñžo,nií)î v F9±“9”fK¯Ÿ‰æñéUa̲ÕÖ>q>ÎÕà¨?ºø–S¸x|­cÄϧÀ¥å-¯q°'ÊzjםÓ4¨X(_Ya*wÎ*|2°Cê§ý)É –„y™›±¸™<é7¢üÔð' 4/6‚á O¾š~Tš¨,3 ?@X¬˜%sBa‡n̨³%S±lèÆ,3På˜yÔðŽŒOa¤F§¤ÒpŸ³¤ž„ô1‹õE›-ã¬J¼C¥îÀ +U‘½†@/¤Տ4/èùº¬^)/˜åÒÐ4S'^{GRïÂFbˆ§D.æÖd„;Ä\)¿á¡aH# ¼]¿pþö¾:rÞ-xúŸ”~ÁàòcX+¿jÂÇE3#øÂqXň^;“VVûØx.ÅAîaÿî«Ð{ϕ:ƒßCi)Ó¹©yÝ>œÑ»‘Î@,f h0¡Å¯XèTrýêg¡m3D[Ä~î>ä +ߋ›§bµúËëÄ¿÷ò7ãâU®ñ&¶ÁƒæF¤KÇ™Kbø~û&槉½Ð3¡÷ÓR'z›à™(Óō$ßþ*öÂ¥Šæ[!Oé|ÁiŒ‘½°—ô•.tÓ;š¢Ø– +“OÉ=[ðHÃk±Iââæ%èO ¼õÔÓ6Á‰‹ÐyJ['.BÙëS%4††&…̆Ø÷m E(éa\¬ÈspçÈþÑã9ÝXO¨ÿ»" H+ñTå¥Xå™ µùëÜs˜¿ö¿Î±;Lœ—IւOúX‹}–©Æ‹g?lKcDˆq2ûóã +{?¬Ýæ¯îª ’pj„Ø?³ V Ì™¯gìý{üšÿ¿LhïªXø{ ^×Ç(©Ñß¢óòt¯Íq‘#úÒ¶f¤$ SÎ7†¹¼ýõKvnN¯¨Ü“#ÕÛp˜5 WýÔÓ`¿ðýâËÁïïÊì¿Æa¸qÐ}~p@¤þI« +’AÁ? N|ÚU؉Lw4$Ð4bd†ŽK/q ++¼½†÷…ÒxZIù¹QZ’¬èAâ9Ívjçüu=W§BÍÑ¥Lè¢éÏ__6îQ¬­âì¹|,ƒP×Wœ¡½øùüÌô×àÒEóB±Y [ ÍÐ/ñø^'Ï/NîÃìeçèÊÔå¨÷àü“»¿=k ÇŹŽ°Äû{¹ÌCï+—žŠï8/¹Œ¥D…Âx®@ŠêßnG*¹yF(ÇåáÅ(’eËX_~ŸŽšêD%ÁP–f(M Òñs˜»ÿ¼]è`g™óE1~Ӎå2—?s?n™=a\·^TuýÍS5˜ý¬~ö†‰ïÞۇÂê­gGåjQ¬¼ÍfÅh[(‡üw ‹Í_~t•M-ËËõ,_ƒ'ä)‘A°s´\µÛDñóV„`øé4ðlöúQö ¾jNÜ›?'bÜB`9X5¿BhoçQ(|ž&5õ‘ãä°(+¹XÂÁãOŸ Rƒïb9ëÁ1R¶sÿ4SÁ Á³î +._x,öŽßP¨°ˆh ²Ë“ Cé”xô^Qù'EòI³O¹ãìt_¯‹Š¿u:.³è“½£KùT¬z ¹Œw„¾ ԭ嚴'ÔÆ¥Nj ˜cñ³y¶q!À4ëùIþ3h8_Mہ‚÷D|e."IXIo}®4Õ öÒ¼Ø ¾š|'æ Ä·ãL0`#™aŽ}ü*åÓåÙm}DŽàyì¾@ލfG½ÌáÆ|ÒcÎþŽ°‰lòi§g´–‹|º=ýÒ- Øà>]·*àëMˆ£ÏEO4›ÞI¸˜Ð½¡n c¡ñø™_’k€¿Ý„ÜycqÛ]-¿ z1Ppâó@?r|‘oDs0/_á± ÎRg±÷žOÇâ{È!dLâËѸÇӃn—¹¿{½O³§…Jáû¹»‡"¬½Üñuò­X­者Åæ×ÀZ‡¥ÜÑ븝ìu„ ¢¿°òOXs?§ù›aêÁ`„[¹Yèq¯X=O‚ØïóSãª")ˆ 0ˆUà'c#ÅdÃçÅN¡5(†û‰Î’ÍR͕j©T#õb8 Pbo?öEp…sGoÄóKÌú€å'Oë‚Tý0r’/uAÏoóÅ/úuÓ4Ï钒R—«Àk)Æ°}0ká=)¢«R¦ÛíÜf.ßÚ áãuñÎ*tÏÈf½€‹ÕÜçÅÀOé©Ó~²€8öWbBþÆ'Êg( `‘e¿“Y>Ç6R{ +¾%~â~¤ã]û±wÃÿüd+åÛw{•@sù±Ôú˜F`Â&öî-ÒPš˜Y$?Œ‘·å§ôí2p`H4ü+ ™3çÕəɃ¼Α03wõntö.jŒÝa ˆ ûŒx]9EÑkàô¥ƒ¿ÁëeZ ë×Åן8oꞾ‚åZV$öé©(¤³OÖ°™ê7?`xz6fXövÚµÌZ3ª½?ùùçmõ³ý²/«|:U ? 5}ŸÀ܄OÓD˜—O^ ˜k=œ·jI(¶Ÿ(;íÜQõ +¼'"úX<÷}†ôù58*BŕB¥œ¾:+¥”ñë³ö™þ[ô+[œ…uÁ¬b#½†˜9¡À'“R¤"%PQÛÛg °¹Ù5.[¥ãè¢Ø‚·¾ ûпmbe€"ÖÔÈ+ûA±=`â§d>)v¥±T'·(~í¿H *Å÷s°cT‡æ™\'®+ÛcØÑÛÁHZPª,‘_L_=ùBÄç{ì"qñò›BFøœžÑp&_ÃQ æ:ÿX€°Eüd*ù ?8˜—Hé"çÍÿ€VN°„îÿ;«Ö[Q¨ÒÊ{…‹KfÞ"þð>÷¨UÛH'ãЗ&R7óƒôtŸ´aYåTðR/­ @ÿ‘'SÁÝ´5V +Q–ë¹´«ŒßÊG‡þ¤oè£Â8å™Ô·Xé‹dSläô¾ ‘‹päô{žD¿Qôñu&©>¸VÃŽ’§·s”ò ²sÒB‘EV} +&ó†ùö…’•_,¸ñø}‘ìàغ|â|Ñï><úøŒû"‹Lýx,úbÄE$NšAŒ>å+„®é5ƒu1UЧWÙ$ŸN¦ÁÇ=á´>S¦=%*bÇ/ŠÙ“v>:½<ÏU¹Y6]9~ˆ “'ú¾$¾<Å'¡y+œäO:$80cKòà:Rˆ…o_±®4 x²Æǖ:Õf¢x4»$Ñ3"A7¤ah”ÍÒÂüžz› ‰î!‰1_i`ÅWbv °Ó‹H¶ì;ÄÇóRijt™NÿýÀŸå!¼Ý,‘¾ˆ¯/×ÖHËì[*sö·Dúvp î¾ ­†”éWË~k¤Ç¾ 8#ý¢5Ò:ùBïQ™°†Ôã×ÐÎы˜ ÒÔw°h–¬‘ÒÍGB –H÷„.8Ià +^Z•nŠ6H™}½‘ÏÛ!ý ÊûOwR|VE[Žågíø£%ƒ_~ZŒŒ´˜Ø›<š»)Èb»dœÓ&Hò+u~‰Ð†–g5þLŸ\"€”ž,‰ÒÛ±`‹45¼Ú›kH5I–жķCÿ½ R¡ÃŒLÒé,ÿš´CZŽMž›ë±ïgöP´B*.>H(xúüj…”¸RVC +XŒ³zи¬‘ÒÍWBx­ÜXŽtO˜ù‰û†RÂËÑÅ¥ÍX™ý@ï+we3R_BœMëix‰½×Bø”Íù.);õøÍc­±|SFڌMH™Fmp/!-½ ÃHŸrDí¹”²BêñÏö*¿3v°Í`´f¤a±o‹47x/äm>‡ˆF¦7ÃH‘Œ™\½T»½èÜiãýôÈéåK圷BŠ,?ý\ îiKïÕö¯v7m‰ô¾FNl‘Þ•å!Fêñ[ŒµJÜO“9¤àw¼=|ä-‘>œ¶ÃVHÁ&#´ïïùùƒ ƒ_hâõê6dôòêëç±x¶Dú:_c¤h}Yë¨9|´C*ï׿ÇÖH¯r!ñ9?+˜¢DB[ú‰16 NW|—DMBÚڛ—Js*.ži„4²¤4WûG¡·Å>@{"šÇú¾9‘¸°i¥9$..S)È+F¤qqöu¾Æ4¤€E6µ¸ïeŸin¶d +_&œ„ô4XŠšØëË W éëfù4}ùD*+)µàX8Pç2¶ü”9¼k=—²6Oƒ÷…㇜é©Ê±~8û¼Íەè{¹}±~z‘~NBɐõÓËX‘f賂ÍóǟQr– lžŽ~'±A/mý´ùZgnZ1é] Ž½†ŸUé^~ûý÷^±‡O[ÔÉ^”ì8Ö{¼æ{W=ë·?‰×ïÀÏٞåSÿÃu÷>è»<µz*Š'ï×2w ¢çñåçvu͏ÐÓ%#$æßG3ßk hùtñ‘ †ýǾüt‰cðü$Î=„[ÚÛÙièxªF|Sl¤²\åà[/¢òQ/¨¡kšé# _¸zDú¢Å›_ôþ âÊ÷î­/øZ ßê(þ,øbÕwð…¬ôZöd2j9ŒOܸHŽ>.p°ƒ£¤Ïcí~¢Ò‡·W‚p'a´›â8©Çä`ç`¢_‚³ûIdþÏGR°Ó>¸è×}=Z:ôbtOx‹Ù"%ž¹²AÊìC”4N½kh H›oH˾”=ÒrYlªH“ȻСMWö™Å‹‚´<42øU”nèÙ{»Ñ!íîkHËÑðîRCkb0’¶©5RºÙ´Gº'|­4¢Å±ƒ Rˆ&!vhÛ!mÙ"EÞx™È؎{$¶H‘?rgÇÞ¨†Tò.Œc=;4Í*_£Ç¿Éq¹èºêwµß3é¾MϽ«Ÿ‹~ââ}à×Y 4fÕZ~œŸšTގ#üdv)I?üV@þßfŒÂYUãËW7À㋨üã”Ð¥op~ŕtôF¯OõC0ç‚Lwë†B3“ì4ؽ5g=ŸøîýèǾŠ lJsÉÙ ç¹H†ùuJJ 4³—Í–üº`5[Ndé:ו.òpÕܐLœ§ü~ü ƒ!А)»RGPŒdÛɊŽ:¾—ïêðç!Z¹aŒë‹=Q3Q&’Ð^`ÿø)åk0MT 3Äbá|m±šéøǍ>&·ß)Y­YÏã׏ÿh=­fP›¿äéýíŪù‹^Èò‚â!$cË#jÂÎÌr?åÆƲ³€ÝÛ »ÇïBÜ5fŸßG·’¥É•Ê1½dMÈÖA ²çíøžÐ$ykηgÎ|÷81ËdzšÑ±Ùô”^ +S=uôFÓãñ¯œ·Yz•UIk–«‡²Sf©•¥—«¹#=ضGяg}Òu‰w%AW­Ì¶½V†l‡–<8ªÖÖšq“Xý²¿šÕ‡’ÙBôZïwÚ¨ô2fUèÊڌ6£Sã2±b@6¦ôRŒ,Œª†9'zõø¡ÑW×OS¶üò‹Ù¢æ“7cÌg&ød·J_«BÕIrÄX!þì˜5(Ð:C6Û¦&f½kíÕ~]蝳ÖaIn j'›¹Œ–¢Òy®È±µX´“{VӉ<%ㄢ²ÄIl Ùhí]¶²‘øžDNLtaIÎüÚPFýRüwn=È(´ðÂÌSbØKÊÒSӔÀÛw¢£­ukzÊÈ«›…i•ÏhÅnÙhUu˒ǿæüҋõ½'kÝâÛ>gf¡ñ%V’Äí9¬•=.\:À÷½oGRNÖ}·3hëÒÉFö}pôù 3¸Ê¥s?¢ºT%IÞX7Ð*9Óåñ»¶*îp¢Ë°'ŽÙÉüúƒÔ¼ºpÌhi·ä˜Ñ¢­Í19³% Å?6cÆÀµ‚T¥´¾w¬ÑŠóüˆžyv…åÐ9´Övì«bë^{üK¶µ¯P1Gâ[håW…z]ðÕ5ei7wy&ç§~7r$¾>w܇}ÀÙ·\âN¹>r›F°Kv¦;}q1MN!ž‰ÉS²$e•pCˆÙ·Üˆ'«Ô^!D‰‘Á“ +Y.Qüã;½^L.miª)nÃ’ Þ$¿Ñ§¶ƒÅùzl’gÒoy óa£ †íْäñ¯EԆ@•1ÊýœíÈÀø׍÷mÇwš¹¾pf¹Ç%ӋsÍWp•Ï0xòú\õ:û&w#T M]ºAZ_œsv1ùÏñIî5]ȧgõ çnUÜ6쑣$ĬÔ̚êMÆÇÉܤàȎ‘ç Q÷êìÒ¹YÁí2$N¹ð“œ©±õߗ2$Ƀ J¬ +Š]¤dÏ5ÿ]‘äõÓ$"è6½,¿õ"t¾´,oÞ½ÙàÁã·ÉI¸ÐùÂEŽÎ³"K—<à~i·Œ±e Æò.®“±Ë×wìò5FIÖÙMC¦ÁèJ££É„Ñ•®š]i#ó=~δ<™ëý y§øc=ö{R«;'âñÖ%+uî§ÇoŸ«-laÑîÖ·hvЍX´ÙÀ¼[íރ·f¿é£¬•.w0j{O©!Bco[Ý¿³²hèþÝúÍ*wáloÑîv³÷Šá¼ÛnåˆÞü.ÅÇc›Ùº2LØaA¶­}Õ·”I @œ ›u¹=øàâÌÕã½m í~K¦ÓÊȪøõÌ,[ÇÈbÝ·5³Íéú§ÌÊüø¥=þ­tä âά„cväÖ¢äH‚³õ ÅÒ\Þy_ Ç^sl]rËÝ lͬŸÓZ•sÆÕðaÿÞ ¤/¿x-Ü.âC֊Lµc«WCæ&|´] ‘Û:çR€êl¿Š=¬ŸÒ²\Ŷñï Päµp«UìÁÊ¿·‚âñ¯„ãj5t^  ÓÚ» ¶Y Mk!:ÕÖBËÒٗŽ#¡Ñ?Z­…ÚZ©?<¥ðõ€®7ÛpÔÀJ¬•Î ,¬ð(]xºŠM`®҅§  R«² +Ž¶Öȱ±ßíLJÖÒ6"zt©çN»§  +(?6<ØXùÕÇéÌ$Y+©$É.ÔË´0Å-–¥¦í²d›O¶]˜Ú3‡ Íæäš}¼ß4Ÿ¥·c¥ å‚Hœ8ÇFn¼Ô¯yiÑÍËZ»7Øܵ¯ä˜›|²Ý΀q&‘$Žß:³q1 +ñ„Ãá[tBU–‰„ ¢œÒÂÊ¥®ÈrHs¤Cáøôßf Þ°¾è÷OUÆÕ|±tìݪ‚ÎãßM sÝRÅІ5tÎtRôº} sSµà:5tÎtÆjÁÍkèœ+è<þÝÔÐ9WЙª7®¡s® óøwSCç\A‡Ow젆ι®~ÚA s> ·M sì­®¡3H¶¯0›Zyێgàí+r¿k‘d—é­Š«nŸ´ƒ.ê¥t^ߖ'kë‡+N„ºÏôŸêëœ%·ßã«ìOš­Å§å*›å“fa—Å`I㟰èÖãÃìêÊ9÷㋯Ò×L7mæ¬&ɾòqɤå’9”]³hΝ­iFujm8Û³^ÕGA$Ý·; òVÚÍޜtvÔ²˜h½¡9ºî꩛•ÅnƓë%•õeی±Rìf½mcŽ’V»mtÄh-1ûºúÐte‚@©Õ Òº¿&0ã,ˆYË㎍`ŠÉ-*]Ú¬ÖÞ£ó]Ðë1|ÃpiBÛ"ÓµJLå:>·%Œ«c{Áj/[珙Sg«‡‘å4I·lU@´ÙŽÕ m_ØoU†Ï*Øց=ϝ݉uêøœ?]àúœ]·l{ÂXæJKZٗ¦eç§ç6$9¸ÒVx Êþ ¼:u®æ;Xu~:¾±ó·öרã[Yc/ †^Xc¼ÉÒv>ì+SÖf\U¶äØˊ¨e-ŽÙo…¬=HÙ¢mÊ1câ——:ͳ3“?¶añUYYÀ(«ºmÞÒ¡^Îl-ìhǶòñ–”~¬Ðîù運 Æã&Ø[Jí:{6߈ øæÞá6 P–eÔ¢^µ™¶á‰¾TnÃZÞ±yìKÜVëþjvØo1šÄޖºï*¬b‡“çRqþä‰úµw~$õ:kǍ~äÙªªX©bȱÖG©ô¯“¶X +•Oɱ@ÎÂêØWDYÍÀFþäw›¶Pý1[>̓nÕÕ¹Ì.àYA”[>™wqŒ" TعU•q¶$™ê‘Q9ÛZ’’ë­bNvî22«N§»(Šs ÉüíŽSò|fÊÈ$¸éŠ˜ÎMF攬îmJíÜEFFÝã[6ŒÎ·ÉÈï «í‡f‘‘ÑNw¸/C[?#cQ-Èýnz@X?WK©›Õ筓‘±Éó#Ƭ:iï²8xø’dw§$r‹¥Bó̵oõ¹q7Îò´º‹šÄÓ;ÒùĀ›@CÊÂUw“ÔÁC³:9k\‘Ý”¡„Ö?$gZ_¦UWGV–™Ž.ØU ®ª«[ï,£å ÕªÛózÎuuÚçX)¼ú€.®³ÿH•^’ÝÕÌûÓ±uh :ŸÈ’:ÏÊï{î¢N:³"-¾u=Ü&ß[¿Îúô®ëá¶:¡êºnÅ ÕÕÃa›¼µ®ª‡³ü"¨íùÀMëá̧¡”í¿ÝÖÃY|wÛq=œ‹¯ì N7/ú#};®‡³ˆÅl¶uP&eÓ :S½˜à,@®ÏDš>1lï[º9¹tJb3Ý¿Û¾¼!—'!WÂq:ìz‡Á1ÏëP£úcζ5ö”å¸Ùú üj‹vç˜3Ÿw–ußQ -¶òP ›s!ŽA ÕÛ¬+½vRu5÷lïÐ+Àîí5ÇƎ٪a饹A9©É~ï@ ›Ó| ÃqRCwÑ+†³…ꡀnù­ Žý~ºõ×´lY#`ÆO̬úšÔÒ×Ñ#¦j[éJ;ÔºŠÔ‡]T¤¾üî°"€í®"õåw©ä^j©­½‹¨3w©g+»©«sõU‘•ÔX†OëV¤ºÿ ´>$¶>ׇÄᣩë2B_ÔÉ£Kj˜ ®˜wy-¹Î֎í´ùc+Oô¹A)œÝ¼ì¶n«¼¥ëR¸õâÊMKáLß zÿ-¥p–Y…—ÂÙåÇ\Æg°~º1¦ï';>H¬÷Ex³?ÖÜÙÕPÁ™’>vþö ‹2füù>L{füÈðz› ÆjôB<áj Çáèªò[ÊÑ鱸(n6‘äR"tÙQ‹åAÎ]K·ÆYŠ³âúá 2ÙÏçO¶ø$Ü7r‰9_+‰o'ï'·ÅéñøÄùCq¿wÞ(žF·'“¿•ëÐ3P_„.Èî%Ÿ§{uùä{‹b·ËS}-*ØÁ;‰J±Ûþ}³®O_JÀ²™ÂsÓ®ØíѶê ÝÌG:TØákµm2ûèRíW»b·vÓ¤=Rt­¶-Rt©ö—]-VÈXag*v«§(Rc ¾jZEj.vCdí*ìèC…ݞЊÛ"%„êqÝ)ºÏO_?ìŠÝޝŠÝ{¤åëý' éÒ}|þ«þEËé{/«÷¶HÁŽ•Jw‚qV¤(¿É·Ù-Â' Wý’ÂÔOÒsOâ#œÉ»€ÉLæ%mé„17i³#ªEI‹µàtæ~å‘[³ lM…Œ{DEœÜ•öœ˜¾ª±Í¥_Ο_:seOTaÅyRçsjôº³›ä¬î‘³ü¦ÊV7É­™…[úèÙÆՐŸXñqã¹>ÇKäVœëÛÑ%r¶ã³¹÷ÍåYݕ$­¼kÀ5Ó]¥4|KmóûãÜëËdÕ÷ò­Ž»º»€nU¦wýjºMó0ëUÓYÅZnWÕtVµtÖ߇٦šÎ*'h¡/[VÓYÕÒmQù¸Æ6¶íIû«éLl1×V祥Π+ÔÊ/„¬]MgÔ»[‘ש¦³Ú§Ñ­•;ª¦³ª¥3íŒì šÎ*Ïbü.Ü.ªé¬ríZôº«j:‡ï\í°šÎª–ÎxvtÕtV‹6žýVÓY‘dþ²ñöÕtVóçñ¯åžº¨¦³š?ËÓP[UÓ-ƒZy§ðÕtö¾å.«éÖâØÆÕtˠзÔv]M·)ÇÖ«¦³Š°<;¯¦³€k«wZMgÀ³ój:«Ý’¥3ð[WÓYÕҙ£×í«é¬Ênj;#»¨¦³ª¥3¯/ÛWÓY1Ãp[ÖNªé\Trí šÎª–Îá>¾íÀÓ`QÄ é~±¯.†Ë1Ÿ‰ïws¸+|rm-$ïb×÷ÕY‘äè]lt_£wáŽO+o¶5©Ê%sø™ ǝçZ‚mÕ³5QË$¹2®n—[Kž¬H’Nwœ¹¨§wË'«ƒNƁO=·Úk¨ä2FD{Ï£sã’`qÌÚ!3§[Åì/º[£fÍúš;ãw®Ü¹äë_sgoaÎ]컽æΡ’Ë]!«o‚;ŸO–/ºÛr@ç àضÅ-.®¹s“Q<_ÿºی¢ÃEwnj• +N×Ü)µoëœ¶Í +%Oï"ûVc^ë;WÓêÎê,N3×®Š_WQ‚¡e‚[×òL«®Îb€|ªgG­ éV\W°Z>«òyþÒ­<ûëqUahˆÃmUb ³“CdUÛԟY’ÝÜ°…mär=Q}d»ÐYÊn¿•«Å7¸PÁ|ª±»ÓP]ž†j¸< µâ`s}äVùVT>†¶Þ +ÁPl?îñ¯g³%Ï%a8Ûj †b²Ýv•Â«Ova`« kn`7ÖªŸ(ÖiN÷ðÄy•vÿU@¬0_Ëó÷ù0ÊÕÉpÝÚeËʟ^ØÄJ÷½:8È[ ™OPC›ù’¦‘¸í껵\w‡›òvvƒ!Z+wV±y?±-dXG÷gW ­®|< +oëN–¿R¼Ñn5†³^=§Í=VKŸ(ÞtTæ{×¼ˆÇ¤ ΅ kîð"ß+¼TÈø]Y™âR ·ºáN«¯´¾ãn#5\ºánó{«×¹áÎÁÇwÜí¢ò±ÙÑMy+ÔÇõMy;©K²»ãníQY~Ú{©.ÉÍE“kßp·â¾×èrkõ wn¿jŽ©ì¤°K>®a½º/¬mí½¯¨…õ¸/¬míµ-S.‚PCåã +k_~­ªÛ×ýΕgÝl–Õ7"Î +k堓s,¶ŽýwáôGkt߄´-czX«¾ÝB£§Æ]яPÌB ·Îkéî}³ÿ°ŒÁ pQÄô.šb-ã*¶²Œi©"Ì]Ø®›ÛÀýqëÃX:­|t•¦vSÄô.šb÷ÍãJTè鶈I½SxÙpGïí óZŽ¡T‘w[ÆäÊ1,Äc&Ça)ÄW¬%.æ­cˆÏ]¬[ãÊ?~Û~©Áò›õÒÚm+ÂV„Enwl$Ž™ïhÜ0Œ@Y&'7ña–Îo¬Ü\p¼[pûëÛ3¼ä¹ªz^mњŽ_˜2U=;Õ¸jÚK$„÷˜>¥Ì¬WÅSÒ/•èݞ²M¿ø$4o‹O%1—¯0·ç>Þ)øDãlL•…Ç?4’'g—L÷°ÝM‡ÏVõp?*N{v¸èî:S׋’¡.rÔ¹²+ÂK}‡ýǾ‰Þ& ¶ìkÿf{B7a‹”nùk+¤¿t›¾J̌ôÃ隻§Cj¬g™ÈL—»0_tG?~׎ljÿö‚¶¥iâ⃴,ÂŽatwΙÊðL·ëGúìTù7¢ 9XsíßäöÖéa­÷ݵCÚ³BªÜû–¾¼~´e0Qº|.Û MW —š‘^c¤ºy«¦LþM.ÂÌ,;u¿#Û~òYk÷KÍD:T—úÉËd±p:ÅÃ¥»ŸóÄ9«ÕÎéØdDúºœé³Ã‰ÛócNkósѸ¯@¹ÈØ—$ýº%Iª€p j­£0v…MȎíèhÕsÑö`•1ë"“T˜ ¹˜:«›Yž‹ë­r(3yróª´U'4ås°®äibÎMÏö×9²¹Š$£hZè‹k¦»?¥…óÉN¥€öyâ5õÅáœVN¢gùl¬Ñ\Å'K›×ͨî¾LëJ.wæê­ävûÒ!‹¿·“¯Æ•Ðìï¨é­´ƒ]ÚËöù±·Ò¾Ûsnµe½æw­7Ì,›ÏŽ–vñZTè¶^leԂ€ÙŸÒr“3iSsÕlk¯ænZ÷·–pA—±ºÖØÊË×áØÊû}Ü’Ú%ǒ»äm l©\xÙ;Ôôe“*@·5€æ݈¹=•¯j؁XYvìªÍþæU€æù³«ԝPÝ  +й†ci_lÃ*@“¨ØF˜Ëç`ífh›õ$ŽmZh˜‡À¥+[vls£ž.FÞ  +О$c®Ä´_)ѳ4*“÷´ö¥|kÝȶñ¥|ÆÛ²þ]—òYev)ßêÙvq)Ÿ#vÃ'ÛKùðNâz7àmp)ŸUn÷—ò9ޏ¼³KùÜÞ_¹Å¥|†¬"ªF¿Øuuçª4ØæV¿]| +Ý뷓J®}j»{ýŒC36mpÚÖò^?笐EÕF÷ú™¤Òt«ß¦ß†2ßë眲Ä×¼×Ïf†ä[ýì¾ µî½~®$yë{ýœO†è2W[Ýë碒k÷úI•\vé\u}Ùò^?ç¡-ß_¹Ù½~ηúmtˆÅ½~β>¡ºþ½~–ÓäüUó îõs>qfó-h#wܖÙé¼¾­îõs^èð.Ïîõs¾ÕoÍ/PÙÞëçêڜ‚^û^?«SSÚ­~[ßÇ·ƒšw÷÷ú9CÁ³¿ƒ{ýœ7Tt'º·º×ϹpÖ¢"u£{ý¬7T”[ýL·3l|¯ßò18ý­~Ê9¥ËSä{ýœ™Ç´vmz¯Ÿå>êBHQÒö÷ú9¸6V¤n~¯Ÿ¡~kéV¿¥ï\mx¯Ÿ3•Ù¶½×ÏQ×î$ïâ^?çÝá5îãÛâ+Æûø¶¯{°¾Õoýjë{ýœ·ˆíOÛ®w¯Ÿ³'/Õ¼o¯Ÿó­~»©J[u>Ããßͽ~ÎûÍæS›Þëç|«ß÷ñ­qHÃé>¾­?¦£Þ귓ûøVÆÒ.¾Þ°|¯ß:Wñ™,Ìv÷ú-[nõS+†¶¼×O/Ë[ý6©J[ßͱ•±5ïõsrs^~!Þßɽ~»¨{]}¯Ÿ»º×mïõS¡¸¼}³{ýÖ;seq¯ßæÅðê­~†û‘õ»Jk×<9Ýêç𕳵îõ³±c²°´Šmx¯ŸóA§¥œÒ†÷úٕÇÑ«8æ¾æ)t§•[ßë·^\¹é½~õt«ß6§õ÷ú9K„Çe’wÕ½~Ύ¡º“¸å½~αڊ¼Ý½~ηúYøcÝëçäI>Ì.îõsëÃlw¯Ÿq&Í·ú9V×®q¯Ÿóâ°¢ŠÓõ½~΋ƒì)¡­ÊøÒòP£H–Ö…å¦ïJ%¸ABZ÷u©]¼‹i«ÎÎïe‹&;FGoŒ»7‡µŽô)/l˜Õ Ap™€R•Ö'»Í'ˆPE˜/øZøb‰ÀE}>xÚóÍC¾¼p“Øk¾1™ƒÆÃ~Ñ?7jÞ÷4æñ3Úï;;èÖ¾rƒËïj¯Áq—/•ßû9­~6¾¯ûûrâbqÿ^ßß ‘ÐO3õs5ªGŽ>§‘§Ü\ô7‚QLøö&½IÂO|…žjû¹fF5¤ûþqQO3u_øµ|á£ø«aä¨Cç8ÍÂíƒ@”÷'—Dùêò[û§1qñ ÌÑË6¸O®´Ìþ–"'éËg4%>\öF”îÙwqöu¾´òªgiäyÁõ¥ÙÙ¼ø$äkÂIþ¤£])Ý#ào¬™5$éÆ÷ÝøÉbnÌ\ú.««±J#]|ćþÇúAúhÈûëòyà£qvBs‡LD-…iz)ÅØÚáˆED˜í g1ŸØIFœ7°´\‰F}*èÕçÃ?4äV‘;!ç[ymhÚê£çÃ)ð€àL(ºôÀÿÞçóp1ÂÑí_¢*9h»8Ï}ÞÝ^æ+ÌÇe„Kœ +ÜÁM·ðz¾_Á#¥øf¤$©5ÞUɖ‚è·`¤õ/„`åìŒ,½eŽ=þB¿• ÑäŒKÁ/G$1ê½Ò‰Öo ­ñèDD ½ æÉd<½Û‹c°Šmßà`ˆH¤èþ8–›ÀŸ™þµéærN ö¢ø·äÁññ‡ðz®Ÿo¹¯ÜQÍ'ÝU=¡Qbü¡>ëümõ:9¬{tGvÕGqýƒ¯“Oõ¡{;8ûV\†ðHÉrm¯¥¶Epg_ê^~‹wÔG1œò4C ¶„¼$¤. T°4"+LBRìö›_…}‘º´§$úJÇuL·¾€°Ñý0×q©S‡) 8×þl<Ù©\á?e°§g‡)D¢)DAžÞFñS*ÄdT¶ÜÆ,Dè MPÆ¿åBo¹#âþ@/’J9¦aš¢WSÔ `& žÝ€%t`ÉÄþì8rw$2Ù;ú2ϾvƒR,ÆS¡§†O–äæ3•Mj³|õááC4*´è&HrNŸÎHIŠ«þ $¹8¼£äÖÅóV{ªj½/­heÓU͞Lý: ê§•@5ï`– @Š>†ßŽÏs‰Ùô[â¢U¿²4¦ûseY<I¡ ˜è´cuvìkmDétÿ ꍣ9Ÿ/3ô4)Ý?V+_äÍ)2ˆ¯ÙÄ7bŸlXÒýÖ^ƒÆQt6Yž Ð}\l|Š'XÙïEIL{uöX2žŸw™gËäX†YJÔè |'‡ŒºÏÍQηQI~¢Î€Æl۟ ÄÕw§²å˞%ˆT»z*ك,{_)| ò]ð§¼Ü¥œ ¡É¹ÀQRî¤%Á ËwÁ4ÌïוiWÍÊÑ;-Kñ¼œâ^®­ÆëÁIâû^øbe¢·—Jhë'nC·Ý^Ë ù‰Ñ;<ÀñiCàì%×G|x½ËÞ½‡Ñã›9r_î eã’K³mé…äú>QçºåM*?¾Ò˜kÞ1ˆ…Då°¢¹tˆ'R==š!°¿ÑK +–‘,ØÃh%‚~‹«m µ &,ÚøEÞÅ<(J‚FãO:Î+£Æ{øP¾ip‚LæÅ?¶n^@@è²ùcÈY”D‡^ñýȊ·ƒ>{ 8:¡&z°/»AûKƒƒy8’ç…UUï¢òßû¹ÃŽòGÑ¯O05¿áÂdì”fÃǒìÕ@.ا$Bº§}å­á{ +5ºŠÿ°ŽÏb²Y]_­1!Txê¾)LˆÇ L U&¼éY@}]«,xÒ³@ù憲“˜gW1¡u|©1!ù<$Óæ“Ñ:¤º»£¹=åKNLˆkÒÿè›$J’ˆ|óٝä8 ‡E{áӘà£gχ*ä@úµÄÅ;Az”dÌޞ_ Àý@ë˦ šSM#œõA3ˆ—ß-‡ñ.jìµÒD{ævòÜË zó-‡ñ½0‰”Wºª¡o}©4d­³õûñ†Ú­hN¬ÀX܃x™nkaêï6FÊílÔÛ¢- ¿»aôf+g#| pZøvF²õï-…ª>X8tßÄÄ·š•ŽKž¸§ZKº9Ò@•‹fGß³ù¢ÊNSG-!¼VŒ÷#¿OUΒz“—DŒiöìç×¥Œ5¿gÛMNs`?»:s1^l£®øntq£a a±çLƒ$cNªÒnoªñ +€oQ“± A ÜL§ãŠÜÏ·Ñxð»ØvEn¶õ>íïô~%;›ª¥úʈñûN’‹Lôæ¹4öÌԐùMRµF/Rð8iJ¡0JgãNLŽÊ”@Q­ILUÔ¸³Ä¡ ÚBþh%Œ#l´Ç‰>\‰ÊâÈQ«ÄÕ×tQ§> ËÒ0•ÐW¢ f”G™°ö縔¹¨ö€ +œÖ²Ê!®{ðºxË)j„‹)"ÄaŠs9Ñx§Ç\bÚ`àX¢r-!Gâå+,Ip#§ò…`C4ݚs2ˆz!‚ºDá ëuý,¦Dw‹°>ÕkqÜ<‚`×oä6“ð£‹þ|¤ð’ýpòôü]<ƒ¡ƒxHɝá¹,òHFÚ¼Šc,I>LõÞBŸ…oF¸â‡éý´>fÅ!D¬”ÇoÚ5í™؛Äú`—êø–ÁÞXæî$SÈß}¾Ùê4›? ±ç(þ9ü&§:}5–TíC›gdaÚ5MlZùÇÁoßàüg}ª%$ýÖڻǩÄþFXúJVP¨ Bu|&ъ¢ -AŒƒ^%Χrì+I}"‹Èã ¢¨ÜÌå`/žèïÕ%yxw¦¤XâèҌáÓ$8+%hP6%•‰•ƒŸ¬¤rªfÁ;÷„¤whëÓÄñi”ä|’ÔÐúèX_”UPO€©'ºq…}æä-ÎQF þ©L2h¥”‘‘Ä&]œµüpo,Ñi(žOèrEê´òM:˜$‰@ù$¬Œï2‚ œ–Ÿÿ﬇ᒤ—#HΛ¸Y {â•ØÿꏽQϑ'‘?#É»qw"ˆ½Þmï_ó⤳õÆsoƛÈ7 +ggéT±×™t{^ü…ŠÔ« °%ì²ä&í¶ŽÐÍb|ñ“+*û7'­â'ÑÌꯡdåm)?à‹dǾÐåç‹~÷áÑÇgÜYd¾èÅcÑ#.(ˆ„›–³:MxbFODZaÂ>$‰®E¦1{ÒÎG§—ç¹*7˦+ÇqaòDߗė'áïÆ`˳¼}Xãlwɤ…_ˆa?¾èý[ ˆxôc­=4¾sôãÈË'ë0êÃ7_¨?Ìúbãë_¤çвçw»R)™ùuÖªõW*4ûë®Uë¯TúuÇíZµþJXÖ^«Ö_©ÔSëk¬Uë¯T2–U‹ +šZ»EÅÅ"åñoÑýú’ìr…Úf}òø-W(m§Ýx:ށ¹UÔ>ãBí±Ò#ÝWÕŒYY·sdÇêH÷+¾hèôÖBíÌj/I Ø;iµ,½äñŽfXý¾bª1•;µ~QBµ‰6è J #M}Ÿ %ê"-+;ÊHö~zhŸ ’|ÏÀ/þ~4'Ò'Èù^åÝÉB—ì¤ÅÌJôçqiáëG3¼Ý“<8*[ƒ »¬*ÛòôàNÞJɲ!M{±7ˆt'{QH®Få•óŽŒ[ìh&”µ±F¨{¤~O’ÿe¤m +XÃx|’7ˆ„—¼~qØÊÖdƒ_:Þ¨œ&Œwˆ)­ƒÒ[Ê´5`Ü[ˆé7:éªioÁç›Õäʬn€£§p9NWòJ²ýd^Þ‰]hi`*È5“†-fßççŠ +€Î¡ ÂÂ't Há±Lj{z²ò¡yy‰~ö®„½8$ Y,ŸJÛý¯ÒÖý«qD۝Dµ½ ,q‹”¶NÒ©¼ç!nðøù‡pKـÀB%çÞÍÛȃÜpûA:‹Ú2÷N®H¬Î6 ÒS… ¹dÄðùï…tBc¢f Û~@>µ"ɶL8Í Æ +Â~ãöCÌí6þZ NV'œäà4SŸk;0¿‹(“ Ò‹€ÂE¼s¤pQ/HŽ;YÈà×±ߪg€¿©¾éF°epÖýn©\ΰÅ0¤ƒØËLZéß_±î0,Îûm3Œ»LÂv[}—̈·±Öö¨ :Oøn£Ý(B 9À»¥Ž ¸yx; +þˆrÏÂålÑZ$ꂏ߁Š;2¶Í>-}c%‘Òö—×x3n¥RÖý%Ó±’•NK^ìàŒÄf{å +¯YKfí ÿêz‚c’{ +?¤ÖÇøÐ÷«í…ÎâuO欴 ª2æĘò[ÜÍüÚÏnù‹Ži2¶Ñ䔧7&C/cf•ƒ\b«aTb¡ÈØfFÄ#ÝÒ Ë˜™Šö›;oOIÝtêVd÷ :ll»™ìÄNâ[i|‡)˜ÕÕ劌OI¥È Å7É3ü¢1/$#Ý¡Ñ|Sbà‹±%½ÇÔxâ·¡ñ*靦|¸LýäÈ0(¿‘A«È±–îô:(–:‰*'WùÇgtØ­W^j ©?? +ëлšè £©=«ýƒZD:BŠg¿ª?D +†´§¾“Ð?˜´C²¦å«.õçcËwAõ|ì¥ñ|,õ èeÇ H5 )¸C2}IJQgåôýy­‡ÝþŠ Î^ˁ)ÈbFÝaŒ^àM.‰’‰ë„|rõô ±íZÛ¹¾Ã`Ñ]*Çû8©ƒnÒñË)Ÿ„æVÇ*tš;©T%ÝÎoøaü+œ¯w>kÅ꙯a†ŒIkÓGx*TŽÕ䬎‘׆x];ÑÒ½©yý W~}J勃~é©[G©ö[R“^|o²šÈk¾%ñº¥õgSOYõ¼ñCL> z]@æá!¡ˆýÀn¿¢€ëTò’¸ b‚eì!‰W+'RÊÙÔ _âP:".¿x1–%þª–¦DMayÁÇÂQxHÈÇÂJ-9s…‚œ J©ðÛqöTK¦&¨bì9‚MÕ¦–¬k¨:ìQ=í©ÜàªgúނúaÔê‡®å*_¥Ô§_Qü7e^ñHÉÒ[÷Œt1&)¡”gýóøu¨ =àCiSÂtb\ÙoàÑj—ÇWÎEðÑQå`ð쿳^´Ýð^wõ[ ¿Z½ùbŠ:¤ÞùÞW\kýé‰Ò+ýCÀ?èÿT*åe9ô/áM¡ÆZÛÄ]½TÈ[{ü8/ö;óþdÜÿx3¨éñ¢vwVôf¼Rßwè{ä 1Ä;ô†G!´½ñ¾{oþ{üpç§ð£]yˆ8Ť¼_B&zü?Îá—húÛK{/¼Ï¯„·‹^¾ñCx92O' Ò;‚¿I2N°©5’$KÆiŠà¼i–‹Ãœ¾) SގGkbÓvSAƒÅÒdœaáF§þo%‰tœÃo©]’t:žÂo©€Ô& Ö¤¥ÂÒH7Œ®ãùD\›ÂÄ,i®*™Š³t’„T<ͦ9ćd’ŽÃhcã$A§<à9–ÄM4•fQ©4qÀxŠõàMŠŽ³). ³ÏÅi:M¡~L<™ i’&& Md*žfȤ7Mý4 E¯©I’¡aœé8Å¡7“\œå ÃÄÓi ‹ â ÏÒç˜t¿H¤â$‡Y"N£FhJÆSÀx^H2À x!•bošÂ(¿I¥SÐÈBc +¢9ԏeâ)Ža¡ xL)ÄG ‡H’A‘øM†oqI%½¸IÜÂ0RK’Má +M´¡!õI¥ÓX$€ëTZ‚çM[<Ťi©)‰_Z`‚}¯'’ ©‡•JÆY†dõ¡‰Fs¤Q-™ÖSžV&“ŒaxtÈ¢%.P0UÐ …¢£€a)„dä1M³èè-¡–‡ÿ ÀL˜–$f¼ÔÃß0›Åo0è` zN !gñ¯ =è Pº‚@¡†$j‡¿P·4AâÇð&L3É »CÊĐ\Rnñ¤I:Œ:ÎbyžsHªIæ$C´²é8ä±V"š¸Í@# òš¤6aŽ0Þ`ÈûøàhùI"Nr´Éò+FËÏàÈÖhå-Ú´—wnù Ä*FÞ@¬jùUÂtVÞ¢m÷ĪvÚ@¬bä Ī–_#L³òmÿb;­'V5òzb5˯#Lµòmÿb;m V1òbU˯#Lµòm»&ÖÞòhWŒ¼vÕò«tꬼE›öòÿ ˏ ÁåWú®¶üjO–_õÿ[þ–?¼€¿—Œ?ʑI»¿#é/–å ôàÐÎ,HHTÊËJyci—Yú팟ãÔÇ$“Ö½þÒmc£¿Òdœ`ðþ±ò*GQ*dùw´ ­ Vc*Õ·u4wÔü#ޑ¦A‚H)wJBE¤Y­‘„¸93–€eâiÚؤÛo—› †úßÓ33M ÃÍ2 [€f¨!óíPx¾4· 4¡Xÿéƒf¢Á…çÛaBpGÁÍB8Åwɐ¡Q'Èr ×¼ðª]]^UՀÄôԐ¢Ä̜Ô"ÞôâIJT…ļ> endobj xref 0 459 0000000003 65535 f +0000000016 00000 n +0000026058 00000 n +0000000004 00000 f +0000000006 00000 f +0000028981 00000 n +0000000007 00000 f +0000000008 00000 f +0000000009 00000 f +0000000010 00000 f +0000000011 00000 f +0000000012 00000 f +0000000013 00000 f +0000000014 00000 f +0000000018 00000 f +0000026109 00000 n +0000028838 00000 n +0000028869 00000 n +0000000019 00000 f +0000000020 00000 f +0000000021 00000 f +0000000022 00000 f +0000000026 00000 f +0000026180 00000 n +0000028722 00000 n +0000028753 00000 n +0000000027 00000 f +0000000028 00000 f +0000000029 00000 f +0000000030 00000 f +0000000031 00000 f +0000000032 00000 f +0000000033 00000 f +0000000034 00000 f +0000000035 00000 f +0000000036 00000 f +0000000037 00000 f +0000000038 00000 f +0000000039 00000 f +0000000040 00000 f +0000000044 00000 f +0000026251 00000 n +0000028606 00000 n +0000028637 00000 n +0000000045 00000 f +0000000046 00000 f +0000000047 00000 f +0000000048 00000 f +0000000052 00000 f +0000026322 00000 n +0000028490 00000 n +0000028521 00000 n +0000000053 00000 f +0000000054 00000 f +0000000055 00000 f +0000000056 00000 f +0000000057 00000 f +0000000058 00000 f +0000000059 00000 f +0000000060 00000 f +0000000061 00000 f +0000000062 00000 f +0000000063 00000 f +0000000064 00000 f +0000000065 00000 f +0000000066 00000 f +0000000070 00000 f +0000026393 00000 n +0000028374 00000 n +0000028405 00000 n +0000000071 00000 f +0000000072 00000 f +0000000073 00000 f +0000000074 00000 f +0000000078 00000 f +0000026464 00000 n +0000028258 00000 n +0000028289 00000 n +0000000079 00000 f +0000000080 00000 f +0000000081 00000 f +0000000082 00000 f +0000000083 00000 f +0000000084 00000 f +0000000085 00000 f +0000000086 00000 f +0000000087 00000 f +0000000088 00000 f +0000000089 00000 f +0000000090 00000 f +0000000091 00000 f +0000000092 00000 f +0000000096 00000 f +0000026535 00000 n +0000028142 00000 n +0000028173 00000 n +0000000097 00000 f +0000000098 00000 f +0000000099 00000 f +0000000103 00000 f +0000026606 00000 n +0000028024 00000 n +0000028056 00000 n +0000000104 00000 f +0000000105 00000 f +0000000106 00000 f +0000000107 00000 f +0000000108 00000 f +0000000109 00000 f +0000000110 00000 f +0000000111 00000 f +0000000112 00000 f +0000000113 00000 f +0000000114 00000 f +0000000115 00000 f +0000000116 00000 f +0000000117 00000 f +0000000118 00000 f +0000000122 00000 f +0000026680 00000 n +0000027906 00000 n +0000027938 00000 n +0000000123 00000 f +0000000124 00000 f +0000000125 00000 f +0000000129 00000 f +0000026754 00000 n +0000027788 00000 n +0000027820 00000 n +0000000130 00000 f +0000000131 00000 f +0000000132 00000 f +0000000133 00000 f +0000000134 00000 f +0000000135 00000 f +0000000136 00000 f +0000000137 00000 f +0000000138 00000 f +0000000139 00000 f +0000000140 00000 f +0000000141 00000 f +0000000142 00000 f +0000000143 00000 f +0000000144 00000 f +0000000145 00000 f +0000000146 00000 f +0000000147 00000 f +0000000148 00000 f +0000000149 00000 f +0000000150 00000 f +0000000151 00000 f +0000000152 00000 f +0000000153 00000 f +0000000154 00000 f +0000000155 00000 f +0000000156 00000 f +0000000157 00000 f +0000000158 00000 f +0000000159 00000 f +0000000160 00000 f +0000000161 00000 f +0000000162 00000 f +0000000163 00000 f +0000000164 00000 f +0000000165 00000 f +0000000166 00000 f +0000000167 00000 f +0000000168 00000 f +0000000169 00000 f +0000000170 00000 f +0000000171 00000 f +0000000172 00000 f +0000000173 00000 f +0000000174 00000 f +0000000175 00000 f +0000000176 00000 f +0000000177 00000 f +0000000178 00000 f +0000000179 00000 f +0000000183 00000 f +0000026828 00000 n +0000027670 00000 n +0000027702 00000 n +0000000184 00000 f +0000000185 00000 f +0000000186 00000 f +0000000187 00000 f +0000000188 00000 f +0000000189 00000 f +0000000190 00000 f +0000000191 00000 f +0000000192 00000 f +0000000193 00000 f +0000000194 00000 f +0000000195 00000 f +0000000196 00000 f +0000000197 00000 f +0000000198 00000 f +0000000199 00000 f +0000000200 00000 f +0000000201 00000 f +0000000202 00000 f +0000000203 00000 f +0000000204 00000 f +0000000205 00000 f +0000000206 00000 f +0000000207 00000 f +0000000208 00000 f +0000000209 00000 f +0000000210 00000 f +0000000211 00000 f +0000000212 00000 f +0000000213 00000 f +0000000214 00000 f +0000000215 00000 f +0000000216 00000 f +0000000217 00000 f +0000000218 00000 f +0000000219 00000 f +0000000220 00000 f +0000000221 00000 f +0000000222 00000 f +0000000223 00000 f +0000000224 00000 f +0000000225 00000 f +0000000226 00000 f +0000000227 00000 f +0000000228 00000 f +0000000229 00000 f +0000000230 00000 f +0000000231 00000 f +0000000232 00000 f +0000000233 00000 f +0000000237 00000 f +0000026902 00000 n +0000027552 00000 n +0000027584 00000 n +0000000238 00000 f +0000000239 00000 f +0000000240 00000 f +0000000241 00000 f +0000000242 00000 f +0000000243 00000 f +0000000244 00000 f +0000000245 00000 f +0000000246 00000 f +0000000247 00000 f +0000000248 00000 f +0000000249 00000 f +0000000250 00000 f +0000000251 00000 f +0000000252 00000 f +0000000253 00000 f +0000000254 00000 f +0000000255 00000 f +0000000256 00000 f +0000000257 00000 f +0000000258 00000 f +0000000259 00000 f +0000000260 00000 f +0000000261 00000 f +0000000262 00000 f +0000000263 00000 f +0000000264 00000 f +0000000265 00000 f +0000000266 00000 f +0000000267 00000 f +0000000268 00000 f +0000000269 00000 f +0000000270 00000 f +0000000271 00000 f +0000000272 00000 f +0000000273 00000 f +0000000274 00000 f +0000000275 00000 f +0000000276 00000 f +0000000277 00000 f +0000000278 00000 f +0000000279 00000 f +0000000280 00000 f +0000000281 00000 f +0000000282 00000 f +0000000283 00000 f +0000000284 00000 f +0000000285 00000 f +0000000286 00000 f +0000000287 00000 f +0000000288 00000 f +0000000289 00000 f +0000000290 00000 f +0000000291 00000 f +0000000295 00000 f +0000026976 00000 n +0000027434 00000 n +0000027466 00000 n +0000000296 00000 f +0000000297 00000 f +0000000298 00000 f +0000000299 00000 f +0000000300 00000 f +0000000301 00000 f +0000000302 00000 f +0000000303 00000 f +0000000304 00000 f +0000000305 00000 f +0000000306 00000 f +0000000307 00000 f +0000000308 00000 f +0000000309 00000 f +0000000310 00000 f +0000000311 00000 f +0000000312 00000 f +0000000313 00000 f +0000000314 00000 f +0000000315 00000 f +0000000316 00000 f +0000000317 00000 f +0000000318 00000 f +0000000319 00000 f +0000000320 00000 f +0000000321 00000 f +0000000322 00000 f +0000000323 00000 f +0000000324 00000 f +0000000325 00000 f +0000000326 00000 f +0000000327 00000 f +0000000328 00000 f +0000000329 00000 f +0000000330 00000 f +0000000331 00000 f +0000000332 00000 f +0000000333 00000 f +0000000334 00000 f +0000000335 00000 f +0000000336 00000 f +0000000337 00000 f +0000000338 00000 f +0000000339 00000 f +0000000340 00000 f +0000000341 00000 f +0000000342 00000 f +0000000343 00000 f +0000000344 00000 f +0000000345 00000 f +0000000346 00000 f +0000000347 00000 f +0000000348 00000 f +0000000349 00000 f +0000000353 00001 f +0000027050 00000 n +0000027316 00000 n +0000027348 00000 n +0000000354 00000 f +0000000355 00000 f +0000000356 00000 f +0000000357 00000 f +0000000358 00000 f +0000000359 00000 f +0000000360 00000 f +0000000361 00000 f +0000000362 00000 f +0000000363 00000 f +0000000364 00000 f +0000000365 00000 f +0000000366 00000 f +0000000367 00000 f +0000000368 00000 f +0000000369 00000 f +0000000370 00000 f +0000000371 00000 f +0000000372 00000 f +0000000373 00000 f +0000000374 00000 f +0000000375 00000 f +0000000376 00000 f +0000000377 00000 f +0000000378 00000 f +0000000379 00000 f +0000000380 00000 f +0000000381 00000 f +0000000382 00000 f +0000000383 00000 f +0000000384 00000 f +0000000385 00000 f +0000000386 00000 f +0000000387 00000 f +0000000388 00000 f +0000000389 00000 f +0000000390 00000 f +0000000391 00000 f +0000000392 00000 f +0000000393 00000 f +0000000394 00000 f +0000000395 00000 f +0000000396 00000 f +0000000397 00001 f +0000000398 00000 f +0000000399 00000 f +0000000400 00000 f +0000000406 00000 f +0000034768 00000 n +0000034844 00000 n +0000035022 00000 n +0000035933 00000 n +0000052842 00000 n +0000000411 00001 f +0000028954 00000 n +0000027124 00000 n +0000027198 00000 n +0000027230 00000 n +0000000412 00001 f +0000000413 00001 f +0000000415 00001 f +0000034140 00000 n +0000000417 00001 f +0000031078 00000 n +0000000426 00001 f +0000032832 00000 n +0000032895 00000 n +0000033125 00000 n +0000033180 00000 n +0000033818 00000 n +0000033964 00000 n +0000034061 00000 n +0000031306 00000 n +0000000427 00001 f +0000000429 00001 f +0000033754 00000 n +0000000430 00001 f +0000000432 00001 f +0000031673 00000 n +0000000433 00001 f +0000000435 00001 f +0000033690 00000 n +0000000436 00001 f +0000000442 00001 f +0000033263 00000 n +0000033409 00000 n +0000033530 00000 n +0000033611 00000 n +0000032040 00000 n +0000000443 00001 f +0000000445 00001 f +0000032768 00000 n +0000000446 00001 f +0000000449 00001 f +0000032404 00000 n +0000031192 00000 n +0000000450 00001 f +0000000451 00001 f +0000000000 00001 f +0000029401 00000 n +0000030659 00000 n +0000076660 00000 n +0000034204 00000 n +0000034254 00000 n +0000030726 00000 n +0000000367 00000 n +trailer <<94FD791731084F1087F9FE9BDDF38204>]>> startxref 76784 %%EOF \ No newline at end of file diff --git a/doc/gfx_and_css/logo.pdf b/doc/gfx_and_css/logo.pdf new file mode 100644 index 00000000..593dca48 --- /dev/null +++ b/doc/gfx_and_css/logo.pdf @@ -0,0 +1,201 @@ +%PDF-1.4 %âãÏÓ +1 0 obj <> endobj 50 0 obj <>stream + + + + + application/pdf + + + Netatalk_Logo + + + + + Adobe Illustrator CS3 + 2009-06-04T10:50:16-07:00 + 2009-06-04T10:50:16-07:00 + 2009-06-04T10:50:16-07:00 + + + + 256 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FUm8y+cvKnle1F15h1a10uEglPrMqoz06iNCebn2UHFXjHmr/nMz8u9NLxaBY3mvTLXjJQW du3+zlDS/wDJLFXlHmH/AJzM/M2/LJpFnp+jwn7DLG1zOPm8remf+ReKvP8AV/z5/OLVSTdebNQT l1FpILMda9LYQ4qxW98z+Zb5uV7q17dMepmuJZD0p+0x7YqlmKuxVM7LzP5lsW5WWrXtqw6GG4lj PSn7LDtirKtI/Pn84tKINr5s1B+PQXcgvB1r0uRNir0Dy9/zmZ+ZtgVTV7PT9YhH22aNrac/J4m9 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 8qP+r5/063n/AFRxV8Vfnbc6LL51u10ScXGlNLLcWkgR4xwnfkF4uFYcKcdx2xVT/IbSDqv5xeU7 UDlw1CO7I36WYNyen/GHFXoH/OZnl42H5m2erotIdY0+NmfxntmaJx9Efp4q8X8sXX1fWrck0WQm Jv8AZig/4amKvRcVdiraippiqVeZ9NF3awQGUoC5c0Fa8RTx/wArFUhTybE3/H0w/wBgP64qiE8h wt/x+MP9gP8AmrFUQn5cwt/x/MP+eY/5qxVER/lfA3/SwYf88h/zViqJj/KS3f8A6WTj/nkP+asV REf5NWzf9LVx/wA8R/zXiqKsvyMtbi/trU6u6idbpi/oA0+rWU92Nuf7Rt+P01xVkXl7/nGOx1a/ 8n2reYJYR5o0FtckcWyt6DKts3ogeoOY/wBK+1t06Yqm1j/ziPp1z581XyufMsyx6bYWd8t19UUl zdyTIUKertx9DrXvirtf/wCcR9O0rzR5X0RfMs0q+Yri7gec2iqYRa2cl0CF9U8uRi49RirIv+hH NK/6m6f/AKQk/wCq2Ku/6Ec0r/qbp/8ApCT/AKrYq7/oRzSv+pun/wCkJP8Aqtirv+hHNK/6m6f/ AKQk/wCq2Ku/6Ec0r/qbp/8ApCT/AKrYq7/oRzSv+pun/wCkJP8Aqtirv+hHNK/6m6f/AKQk/wCq 2Ku/6Ec0r/qbp/8ApCT/AKrYq7/oRzSv+pun/wCkJP8Aqtirv+hHNK/6m6f/AKQk/wCq2Ku/6Ec0 r/qbp/8ApCT/AKrYqyeL/nFWwSJEPmOZiqhS31Vd6Clf73FV/wD0Kxp//UxS/wDSMv8A1UxV3/Qr Gn/9TFL/ANIy/wDVTFXyZ+alhZ6Z+YGtaTZ3RvLfTLg2S3DKELPAOEvwgtSkoYdcVeo/84Z+Xjf/ AJm3mrutYdH0+RlfwnuWWJB9MfqYq9X/AOczPKp1L8u7HX4k5TaDeD1Gp9m3vAIn/wCSqxYq+LUd kdXU0ZSCp9xir1GyukurSG4XpKgb5EjcfQcVV8VbUVOKpP5k0xbqW3Bmkj4K32DTqR/TFUtj8sxN /wAflwP9kP6Yqio/KMLf8f1yPk4/piqKj8kwt/0sbsfJx/TFUVH5Cgb/AKWd4Pk4/piqLi/Lm3b/ AKW18PlIP6Yqio/yxtm/6XGoD5SD+mKo3T/yntJtTtLc63qSCZbwl1lAYehp9zcCm37Rh4t/kk4q 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 + + + + + + uuid:94E82CBF528711DE830DC7B65C12678A + uuid:a8158099-b802-4d00-acfd-b4ab575a94be + + uuid:94E82CBE528711DE830DC7B65C12678A + uuid:1B95FEC551E111DEA13AAE68EE5F3A4D + + + + 1 + True + False + + 3.000000 + 3.000000 + Inches + + + + Black + + + + + + Default Swatch Group + 0 + + + + + + Adobe PDF library 8.00 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 2 0 obj <> endobj 5 0 obj <>/Properties<>>>/ExtGState<>>>/Type/Page>> endobj 47 0 obj <>stream +H‰”WˎÝ6 Ýû+ôֈ¢Ô¶IÛM³ºèº$-ÐL‹IùûR¶,ûÞ;E1˜±)¾_2çé§_¿}úêž>¼ î»÷ïÜòº©Ø諭~Z~qâÔ~|åìþXž~ü9¸ßþ^^Ù99 +⨉äž_LþeY“øUfñ¹6·ró%ËAëSÜó2ˆ«Ï0²ë®T«§ÜÔÈþú¼\™Wí«ù‹ûKž—ÏËÇ-BBtST¾&Ž®D/UÚù„“‰** rVFϒ4. +¾ ;*>h8oŽT…pó"Ñ¥âc5áêÞrö‰Cv‚¿A‘'_[r{A3°rð9%'XÙyÈ® +t`8±'©®Àk²ô怃âjô­"o¹yʚ})ÀHršËU´Ä¬.Ö"ž‚ñ#Œ*ÉE”d&¯Á—Äz ¶Õ™²(ÃÕNæ. yâ7_Cݕ….šlK†2|ƒÌ1 d »ô¼ADãJAó‚OÈjò@ÃwcW’çjæ)"qªÑk¸JKSE£“oÐ BT8P•Ì|ëA- TrÚ蘌ŸòÐàbFS0· Åh“0 ‚ºË@•ÈP>Ö0Ð ­FÌ_й$´/c†Ð}›4R(ÚíH¬ñ{Ô@âÃðm±£Xó8úbjšOЌ¹L½Ç×ÞäÚ÷ÿL“N“1Whè†@³1 ŽÉ£‘¾,WŠì‰n?ˆÁB _É ¯˜$ªöZÌ¥ñª‡¿ºSF/d›˜Íù@…eäÂlöWL0•ÞR¼!‘aâd;@a5gWºÇ¸Eò…Ø Çë5 TÎé^vYÍŠTÑ Wzd͛ò•Ò¢Çgê^B ë înµDWzö{KiÜ2冼 $!#øædӟ/eíªˆ«ÃœÇ©50ÈFé“9©4®hcÞi­“© ²©­]屚ieg_s÷¤¦ÉgÝø™ øãi9Ï@§Ê)igên’ +æþÿŸį¡¨å£N:Üå “†Ù{Á˜š°ÐYSc¼•â┸¡©„èìâq.'¾¤¡[ì% [™´¬(›~‚#\YUõ.nµ•~;Ô2´K¢V5ª<Ñè]!ô»T{ófûˆŒƒ‹ nû‚/2q61|ZbÃäáºݞ>/ Œ&CV +>¦“€æ@«8$Ø|þO¶ Ât׳@Ñì°ØN¯ºµ‘nmO?¼÷þ¯:V äé è]à-è›Äcè›ÀÐ7‰·¡S‡N ݁*`=jEÄ"«ÿXë_E`Ù$z>ó}üüÄ',i´ó;î™/ØåÚÎî gö u<£ÆvAú~Œz“xˆzã?D½ñ¡ÞØÿš;껫±-üÀ¢ËÓ´ðcci}áÇ`NJ…¼Öƒ´§­jû‰Ö½aûÜuWÂ~Y#Û¿½báWf> endobj 11 0 obj <> endobj 43 0 obj <> endobj 20 0 obj <>/ColorSpace<>/ExtGState<>>>/BBox[106.997 139.145 150.43 135.867]>>stream +q +150.43 135.867 -43.433 3.278 re +W n +q +0 g +1 i +/GS0 gs +43.4326172 0 0 -43.4326172 106.9970703 137.5058594 cm +BX /Sh0 sh EX Q +Q + endstream endobj 26 0 obj <>/ColorSpace<>/ExtGState<>>>/BBox[106.997 133.854 150.43 130.576]>>stream +q +150.43 130.576 -43.433 3.278 re +W n +q +0 g +1 i +/GS0 gs +43.4326172 0 0 -43.4326172 106.9970703 132.2148438 cm +BX /Sh0 sh EX Q +Q + endstream endobj 36 0 obj <>/ColorSpace<>/ExtGState<>>>/BBox[39.6411 84.9033 88.6143 81.626]>>stream +q +39.641 84.903 48.973 -3.277 re +W n +q +0 g +1 i +/GS0 gs +-48.9736328 0 0 48.9736328 88.6142578 83.2646484 cm +BX /Sh0 sh EX Q +Q + endstream endobj 42 0 obj <>/ColorSpace<>/ExtGState<>>>/BBox[39.6411 90.1943 88.6143 86.917]>>stream +q +39.641 90.194 48.973 -3.277 re +W n +q +0 g +1 i +/GS0 gs +-48.9736328 0 0 48.9736328 88.6142578 88.5556641 cm +BX /Sh0 sh EX Q +Q + endstream endobj 39 0 obj <> endobj 13 0 obj [/DeviceN[/Black]/DeviceCMYK 14 0 R 15 0 R] endobj 14 0 obj <>stream +H‰ª6Ô3#C…¢üœbD 2óRR+2\ÉeE +©É +Å¥Iu¦ +ºèf (4*ä˛Pi3ÒS!ªåÆ0•FŒ4‚[^_ P `.§G± endstream endobj 15 0 obj <> endobj 16 0 obj <> endobj 32 0 obj <> endobj 33 0 obj <> endobj 34 0 obj <> endobj 35 0 obj <> endobj 29 0 obj <> endobj 23 0 obj <> endobj 17 0 obj <> endobj 18 0 obj <> endobj 19 0 obj <> endobj 9 0 obj <> endobj 49 0 obj <> endobj xref 0 51 0000000003 65535 f +0000000016 00000 n +0000026186 00000 n +0000000004 00001 f +0000000006 00000 f +0000026237 00000 n +0000000007 00001 f +0000000008 00001 f +0000000010 00001 f +0000031007 00000 n +0000000012 00001 f +0000027992 00000 n +0000000021 00001 f +0000029722 00000 n +0000029782 00000 n +0000030011 00000 n +0000030064 00000 n +0000030691 00000 n +0000030834 00000 n +0000030929 00000 n +0000028218 00000 n +0000000022 00001 f +0000000024 00001 f +0000030628 00000 n +0000000025 00001 f +0000000027 00001 f +0000028579 00000 n +0000000028 00001 f +0000000030 00001 f +0000030565 00000 n +0000000031 00001 f +0000000037 00001 f +0000030146 00000 n +0000030289 00000 n +0000030407 00000 n +0000030487 00000 n +0000028941 00000 n +0000000038 00001 f +0000000040 00001 f +0000029659 00000 n +0000000041 00001 f +0000000044 00001 f +0000029300 00000 n +0000028105 00000 n +0000000045 00001 f +0000000046 00001 f +0000000000 00001 f +0000026665 00000 n +0000027926 00000 n +0000031069 00000 n +0000000077 00000 n +trailer <<2861F2D7C5014E0FA17536D73E45D784>]>> startxref 31246 %%EOF \ No newline at end of file diff --git a/doc/gfx_and_css/logo.png b/doc/gfx_and_css/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8138256ee07411d3d339fe2bdef1a2288fe3697f GIT binary patch literal 167215 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%0Aq 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 N)!wgFxa)Y}{{ZHs-_`&C literal 0 HcmV?d00001 diff --git a/doc/gfx_and_css/netatalk.css b/doc/gfx_and_css/netatalk.css new file mode 100644 index 00000000..8096a61f --- /dev/null +++ b/doc/gfx_and_css/netatalk.css @@ -0,0 +1,251 @@ +BODY { + /* font-family: helvetica, arial, "lucida sans", sans-serif; */ + font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;; + background-color: white; + font-size: 1em; + margin-left: 15px; + margin-right: 15px; +} + +.pdparam{ + /* font-family: helvetica, arial, "lucida sans", sans-serif; */ + font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;; + font-size: 12px; +} + +TD { + /* font-family: helvetica, arial, "lucida sans", sans-serif; */ + font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;; + font-size: 12px; +} + +H1, H2, H3 { + font-size: 130%; + padding: 2px; + margin-top: 0px; +} + +H1 { + background-color: #424242; + color: #FFFFFF; +} + +H2 { + background-color: #424242; + color: #FFFFFF; + text-decoration: none; +} + +H3 { + background-color: #330000; + color: #FFFFFF; +} + +H4 { + color: #330000; + font-size: 120%; +} + +H5 { + font-size: 100%; + color: #330000; +} + +.term { + color: black; +} + +TR.qandadiv TD { + padding-top: 1em; +} + +DIV.navheader { + font-size: 80%; + margin-top: 15px; +} + +div.titlepage { + margin-top: 15px; +} + +DIV.navheader th{ + font-size: 100%; +} + +DIV.navfooter { + font-size: 80%; +} + +A:link { + color: #660000; +} + +A:visited { + color: #CC0000; +} + +A:active { + color: #FF0033; +} + +TR.question { + color: #33C; + font-weight: bold; +} + +TR.question TD { + padding-top: 1em; +} + +DIV.variablelist { + padding-left: 2em; + color: #33C; +} + +P { + color: black; +} + +DIV.caution, DIV.tip, DIV.important { + border: dashed 1px; + background-color: #EEEEFF; + width: 60em; + padding: 5px; +} + +PRE.programlisting, PRE.screen { + border: #630 1px dashed; + color: #993300; + padding: 2px; + font-size: 120%; +} +DIV.warning { + border: dashed 1px; + background-color: #BFBFBF; + width: 60em; + padding: 5px; +} + +DIV.note { + border: dashed 1px; + background-color: #f2f2f2; + width: 60em; + padding: 5px; +} + +DIV.author { + padding-top: 1em; +} + +DIV.revhistory table{ + font-size: 12px; + border-collapse: collapse; + border-spacing: 0; + border: 0px; +} + +DIV.revhistory tr { + border: 0px; +} + +DIV.revhistory td { + border: 0px; +} + +DIV.revhistory th { + border: 0px; +} + +div.book, div.chapter, div.refentry { + font-size: 12px; +} + +div.indexdiv { + font-size: 12px; +} + +tt { + font-size: 12px; +} + +/** + * netatalk + */ +/* definitions for the header */ +div#header { + margin-top: 15px; + margin-left: -15px; + margin-right: -15px; + height: 109px; + white-space: nowrap; + background-color: #424242; + background-image: url(/gfx/bg.gif); + background-repeat: no-repeat; + background-position: 380px 0px; +} + +div#logo { + position: absolute; + margin-top: 0px; + margin-left: 25px; + width: 225px; + height: 109px; + padding: 0px; + background-image: url(/gfx/netatalklogo.gif); + background-repeat: no-repeat; +} + +#logo img { + border: 0px; +} + +div#menlinks { + position: absolute; + left: 272px; + top: 102px; + font-family: Geneva, Arial, Helvetica, sans-serif; + font-size: 12px; + font-weight: normal; + color: #FFFFFF; + text-decoration: none; + white-space: nowrap; + +} + +div#menlinks a, div#menlinks a:visited { + color: #FFFFFF; + margin-right: 15px; + text-decoration: none; +} + +#menlinks img { + border: 0px; +} + +.italic { + font-family: Geneva, Arial, Helvetica, sans-serif; + font-size: 11px; + font-style: italic; + font-weight: normal; + color: #000000; + text-decoration: none; +} + +a, a:visited { + text-decoration: none; + color: #660000; +} + +/* definitions for the footer */ +div.footer { + margin-left: 210px; + margin-top: 22px; + font-size: 75%; + width: 145px; + text-align: left; + white-space: nowrap; +} + +.footer img { + padding-right: 5px; +} diff --git a/doc/html.xsl.in b/doc/html.xsl.in new file mode 100644 index 00000000..b2fbfd3e --- /dev/null +++ b/doc/html.xsl.in @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/doc/man.xsl.in b/doc/man.xsl.in new file mode 100644 index 00000000..fca9fec8 --- /dev/null +++ b/doc/man.xsl.in @@ -0,0 +1,26 @@ + + + + + +1 + + (//refmiscinfo[@class='date'])[last()] + + + +1 + + + + + + + + + + + + + + diff --git a/doc/manpages/man1/ad.1.xml b/doc/manpages/man1/ad.1.xml new file mode 100644 index 00000000..3163d067 --- /dev/null +++ b/doc/manpages/man1/ad.1.xml @@ -0,0 +1,402 @@ + + + + ad + + 1 + + 02 Sep 2011 + + :NETATALK_VERSION: + + + + ad + + Netatalk compatible UNIX file utility suite. + + + + + ad + + ls | cp | mv | rm + + ... + + + + ad + + -v | --version + + + + + Description + + ad is a UNIX file utility suite with Netatalk + compatibility. AppleDouble + AppleDouble + files in .AppleDouble directories and + the CNID databases are updated as appropriate. + + + + Available Commands + + + ad ls + + -dRlu + + file|dir ... + + + List files and directories. + + + ad cp + + -aipvf + + src_file + + dst_file + + + + ad cp -R + + -aipvf + + src_file|src_directory ... + + dst_directory + + + Copy files and directories. + + + ad mv + + -finv + + src_file + + dst_file + + + + ad mv + + -finv + + src_file|src_directory ... + + dst_directory + + + Move files and directories. + + + ad rm + + -Rv + + file|directory + + + + ad -v|--version + + + Show version. + + + + ad ls + + List files and directories. Options: + + + + -d + + + Directories are listed as plain files + + + + + -R + + + list subdirectories recursively + + + + + -l + + + Long output, list AFP info + + + + + -u + + + List UNIX info + + + + + Long output description + + <unixinfo> <FinderFlags> <AFP Attributes> <Color> <Type> <Creator> <CNID from AppleDouble> <name> + +FinderFlags (valid for (f)iles and/or (d)irectories): + + d = On Desktop (f/d) + e = Hidden extension (f/d) + m = Shared (can run multiple times) (f) + n = No INIT resources (f) + i = Inited (f/d) + c = Custom icon (f/d) + t = Stationery (f) + s = Name locked (f/d) + b = Bundle (f/d) + v = Invisible (f/d) + a = Alias file (f/d) + +AFP Attributes: + + y = System (f/d) + w = No write (f) + p = Needs backup (f/d) + r = No rename (f/d) + l = No delete (f/d) + o = No copy (f) + +Note: any letter appearing in uppercase means the flag is set but it's a directory for which the flag is not allowed. + + + + ad cp + + Copy files and directories. + + In the first synopsis form, the cp utility copies the contents of + the source_file to the target_file. In the second synopsis form, the + contents of each named source_file is copied to the destination + target_directory. The names of the files themselves are not changed. If cp + detects an attempt to copy a file to itself, the copy will fail. + + Netatalk AFP volumes are detected by means of their ".AppleDesktop" + directory which is located in their volume root. When a copy targeting an + AFP volume is detected, its CNID database daemon is connected and all + copies will also go through the CNID database. AppleDouble files are also + copied and created as needed when the target is an AFP volume. + + Options: + + + + -a + + + Archive mode. Same as -Rp. + + + + + -f + + + For each existing destination pathname, remove it and create a + new file, without prompting for confirmation regardless of its + permis- sions. (The -f option overrides any previous -i or -n + options.) + + + + + -i + + + Cause cp to write a prompt to the standard error output before + copying a file that would overwrite an existing file. If the + response from the standard input begins with the character 'y' or + 'Y', the file copy is attempted. (The -i option overrides any pre- + vious -f or -n options.) + + + + + -n + + + Do not overwrite an existing file. (The -n option overrides + any previous -f or -i options.) + + + + + -p + + + Cause cp to preserve the following attributes of each source + file in the copy: modification time, access time, file flags, file + mode, user ID, and group ID, as allowed by permissions. If the user + ID and group ID cannot be preserved, no error message is displayed + and the exit value is not altered. + + + + + -R + + + If source_file designates a directory, cp copies the directory + and the entire subtree connected at that point.If the source_file + ends in a /, the contents of the directory are copied rather than + the directory itself. + + + + + -v + + + Cause cp to be verbose, showing files as they are + copied. + + + + + -x + + + File system mount points are not traversed. + + + + + + + ad mv + + Move files and directories. + + Move files around within an AFP volume, updating the CNID database + as needed. If either: + + source or destination is not an AFP volume + + + + source AFP volume != destination AFP volume + + the files are copied and removed from the source. + + Options: + + + + -f + + + Do not prompt for confirmation before overwriting the + destination path. (The -f option overrides any previous -i or -n + options.) + + + + + -i + + + Cause mv to write a prompt to standard error before moving a + file that would overwrite an existing file. If the response from the + standard input begins with the character `y' or `Y', the move is + attempted. (The -i option overrides any previous -f or -n + options.) + + + + + -n + + + Do not overwrite an existing file. (The -n option overrides + any previous -f or -i options.) + + + + + -v + + + Cause mv to be verbose, showing files after they are + moved. + + + + + + + ad rm + + Remove files and directories. + + The rm utility attempts to remove the non-directory type files + specified on the command line. If the files and directories reside on an + AFP volume, the corresponding CNIDs are deleted from the volumes + database. + + The options are as follows: + + + + -R + + + Attempt to remove the file hierarchy rooted in each file + argument. + + + + + -v + + + Be verbose when deleting files, showing them as they are + removed. + + + + + + + Reporting Bugs + + Report bugs to the Netatalk-devel list + <netatalk-devel@lists.sourceforge.net>. + + + + See also + + + dbd + + 1 + , + apple_dump + + 1 + . + + diff --git a/doc/manpages/man1/afpldaptest.1.xml b/doc/manpages/man1/afpldaptest.1.xml new file mode 100644 index 00000000..52ec6d33 --- /dev/null +++ b/doc/manpages/man1/afpldaptest.1.xml @@ -0,0 +1,97 @@ + + + + afpldaptest + + 1 + + 22 Mar 2012 + + :NETATALK_VERSION: + + + + afpldaptest + + Syntactically check ldap parameters in afp.conf + + + + + afpldaptestafpldaptest + + + -u USER + -g GROUP + -i UUID + + + + + afpldaptestafpldaptest + + + -h + -? + -: + + + + + + + DESCRIPTION + + afpldaptest is a simple command to syntactically + check ldap parameters in :ETCDIR:/afp.conf. + + + + + OPTIONS + + + + USER + + + Show uuid for USER. + + + + + GROUP + + + Show uuid for GROUP. + + + + + UUID + + + Show user, group or local-uuid for + UUID. + + + + + + + + + Show the help and exit. + + + + + + + + + SEE ALSO + + afp.conf5 + + diff --git a/doc/manpages/man1/afppasswd.1.xml b/doc/manpages/man1/afppasswd.1.xml new file mode 100644 index 00000000..ccd5ee6d --- /dev/null +++ b/doc/manpages/man1/afppasswd.1.xml @@ -0,0 +1,151 @@ + + + + afppasswd + + 1 + + 22 Mar 2012 + + :NETATALK_VERSION: + + + + afppasswd + + netatalk password maintenance utility + + + + + afppasswd + afppasswd + + UAM + + User Authentication Module + + + -acfn + + -p + passwdfile + + -u + minimumuid + + + + + DESCRIPTION + + afppasswd allows the maintenance of afppasswd + files created by netatalk for use by the uams_randnum.so UAM (providing + the "Randnum exchange" and "2-Way Randnum exchange" User Authentication + Modules). + + afppasswd can either be called by root with + parameters, or can be called by local system users with no parameters to + change their AFP passwords. + + + With this utility you can only change the passwords used by two + specific UAMs. As they provide only weak password encryption, the use of + the "Randnum exchange" and "2-Way Randnum exchange" UAMs is deprecated + unless one has to support very old AFP clients, that can not deal with + the more secure "DHCAST128" and "DHX2" UAM instead. Please compare with + the Authentication chapter inside + Netatalk's documentation. + + + + + EXAMPLE + + Local user changing their own password: + + example% afppasswd +Enter NEW AFP password: (hidden) +Enter NEW AFP password again: (hidden) +afppasswd: updated password. + + + + + OPTIONS + + + + + + + Add a new user to the afppasswd + file. + + + + + + + + Create and/or initialize afppasswd file or + specific user. + + + + + + + + Force the current action. + + + + + path + + + Path to afppasswd file. + + + + + + + + If cracklib support is built into netatalk this option will cause cracklib + checking to be disabled, if the superuser does not want to have the + password run against the cracklib dictionary. + + + + + minimum + uid + + + This is the minimum user id + (uid) that afppasswd will use when creating + users. + + + + + + + SEE ALSO + + + afpd + + 8 + , + afp.conf + + 5 + . + + \ No newline at end of file diff --git a/doc/manpages/man1/afpstats.1.xml b/doc/manpages/man1/afpstats.1.xml new file mode 100644 index 00000000..b1ab94f2 --- /dev/null +++ b/doc/manpages/man1/afpstats.1.xml @@ -0,0 +1,50 @@ + + + + afpstats + + 1 + + 24 Mar 2013 + + :NETATALK_VERSION: + + + + afpstats + + List AFP statistics + + + + + afpstatsafpstats + + + + + DESCRIPTION + + afpstats list AFP statistics via D-Bus IPC. + + + + + NOTE + + afpd must support D-Bus. Check it by + "afpd -V". + + "" must be set in + :ETCDIR:/afp.conf. + + + + + SEE ALSO + + afpd8, + afp.conf5, + dbus-daemon1 + + diff --git a/doc/manpages/man1/apple_dump.1.xml b/doc/manpages/man1/apple_dump.1.xml new file mode 100644 index 00000000..1723a30c --- /dev/null +++ b/doc/manpages/man1/apple_dump.1.xml @@ -0,0 +1,182 @@ + + + + apple_dump + + 1 + + 16 Jul 2012 + + :NETATALK_VERSION: + + + + apple_dump + + Dump AppleSingle/AppleDouble format data + + + + + apple_dumpapple_dump + + -a + + + FILE + DIR + + + + + apple_dumpapple_dump + + -e + + + FILE + DIR + + + + + apple_dumpapple_dump + + -f + + FILE + + + + apple_dumpapple_dump + + -d + + FILE + + + + apple_dumpapple_dump + + + -h + -help + --help + + + + + apple_dumpapple_dump + + + -v + -version + --version + + + + + + + DESCRIPTION + + apple_dump is a perl script to dump + AppleSingle/AppleDouble format data. + This script can dump various AppleSingle/AppleDouble data created + by mailer, archiver, Mac OS X, Netatalk and so on. + With no FILE|DIR, or when FILE|DIR is -, read standard input. + + + + + OPTIONS + + + + [FILE|DIR] + + + This is default. + Dump a AppleSingle/AppleDouble file for + FILE or DIR + automatically. + If FILE is not AppleSingle/AppleDouble format, + look for extended attribute, + .AppleDouble/FILE and + ._FILE. + If DIR, look for + extended attribute, + DIR/.AppleDouble/.Parent and + ._DIR. + + + + + FILE|DIR + + + Dump extended attribute ofFILE or DIR. + + + + + [FILE] + + + Dump FILE. Assume FinderInfo to be FileInfo. + + + + + [FILE] + + + Dump FILE. Assume FinderInfo to be DirInfo. + + + + + + + + + Display the help and exit + + + + + + + + + Show version and exit + + + + + + + + + NOTE + + There is no way to detect whether FinderInfo is FileInfo or DirInfo. + By default, apple_dump examines whether file or directory, a parent directory + is .AppleDouble, filename is ._*, filename is .Parent, and so on. + + If setting option -e, -f or -d, assume FinderInfo and doesn't look + for another file. + + + + + SEE ALSO + + ad1, + getfattr1, + attr1, + runat1, + getextattr8, + lsextattr8 + + diff --git a/doc/manpages/man1/asip-status.pl.1.xml b/doc/manpages/man1/asip-status.pl.1.xml new file mode 100644 index 00000000..d8d5b8dd --- /dev/null +++ b/doc/manpages/man1/asip-status.pl.1.xml @@ -0,0 +1,162 @@ + + + + asip-status.pl + + 1 + + 24 Jul 2012 + + :NETATALK_VERSION: + + + + asip-status.pl + + Queries AFP servers for their capabilities + + + + + asip-status.pl + asip-status.pl + + + -d + -i + -x + + HOSTNAME[:PORT] + + + + + + asip-status.pl + asip-status.pl + + + + -v + -version + --version + + + + + + + DESCRIPTION + + asip-status.pl is a perl script that + sends a FPGetSrvrInfo request to an AFP server at HOSTNAME:PORT and + displays the results, namely "Machine type", the server's name, supported + AFP versions, UAMs and AFP flags, the "server signature" and the network + addresses, the server provides AFP services on. + + When you don't supply :PORT, then the default AFP port, 548, will be + used. + + + + OPTIONS + + + + + + + Enable debug output. + + + + + + + + Show icon if it exists. + + + + + + + + Enable hex dump output. + + + + + + + + Show version. + + + + + + + + EXAMPLES + + asip-status.pl 192.168.1.15 +AFP reply from 192.168.1.15:548 +Flags: 1 Cmd: 3 ID: 57005 +Reply: DSIGetStatus +Request ID: 57005 +Machine type: Macintosh +AFP versions: AFPVersion 1.1,AFPVersion 2.0,AFPVersion 2.1,AFP2.2 +UAMs: Cleartxt passwrd,Randnum exchange,2-Way Randnum exchange +Volume Icon & Mask: Yes +Flags: + SupportsCopyFile + SupportsChgPwd + SupportsServerMessages + SupportsServerSignature + SupportsTCP/IP + SupportsSuperClient +Server name: bookchan +Signature: +04 1d 65 23 04 1d 65 23 04 1d 65 23 04 1d 65 23 ..e#..e#..e#..e# + +Network address: 192.168.1.15:548 (TCP/IP address and port) +Network address: 65280.128 (ddp address) + + + asip-status.pl myserver:10548 +AFP reply from myserver:10548 +Flags: 1 Cmd: 3 ID: 57005 +Reply: DSIGetStatus +Request ID: 57005 +Machine type: Netatalk3.0 +AFP versions: AFP2.2,AFPX03,AFP3.1,AFP3.2,AFP3.3 +UAMs: DHX2,DHCAST128 +Volume Icon & Mask: Yes +Flags: + SupportsCopyFile + SupportsServerMessages + SupportsServerSignature + SupportsTCP/IP + SupportsSrvrNotifications + SupportsOpenDirectory + SupportsUTF8Servername + SupportsUUIDs + SupportsExtSleep + SupportsSuperClient +Server name: myserver +Signature: +8a c6 12 3a 0e d9 95 3e 6f 31 e3 a9 17 f5 70 f6 ...:...>o1....p. + +Network address: 192.168.1.154:10548 (TCP/IP address and port) +UTF8 Servername: myserver + + + + + REPORTING BUGS + + Report bugs to the Netatalk-devel list + <netatalk-devel@lists.sourceforge.net>. + + diff --git a/doc/manpages/man1/dbd.1.xml b/doc/manpages/man1/dbd.1.xml new file mode 100644 index 00000000..b3043f3f --- /dev/null +++ b/doc/manpages/man1/dbd.1.xml @@ -0,0 +1,124 @@ + + + + dbd + + 1 + + 28 Dec 2012 + + :NETATALK_VERSION: + + + + dbd + + CNID database maintenance + + + + + dbd + dbd + + + -fsv + + volumepath + + + + + Description + + dbd scans all file and directories of AFP + volumes, updating the CNID database of the volume. It must be run with + appropriate permissions i.e. as root.. + + + + Options + + + + -c + + + convert from adouble:v2 to adouble:ea + + + + + -f + + + delete and recreate CNID database + + + + + -F + + + location of the afp.conf config file + + + + + -s + + + scan volume: treat the volume as read only and don't perform + any filesystem modifications + + + + + -t + + + show statistics while running + + + + + -v + + + verbose + + + + + -V + + + display version info + + + + + + + CNID background + + The CNID backends maintains name to ID mappings. If you change a + filename outside afpd(8) (shell, samba), the CNID database will not + reflect that change. Netatalk tries to recover from such inconsistencies + as gracefully as possible. + + + + See also + + + cnid_metad + + 8 + , + cnid_dbd + + 8 + + + diff --git a/doc/manpages/man1/macusers.1.xml b/doc/manpages/man1/macusers.1.xml new file mode 100644 index 00000000..6f0abb19 --- /dev/null +++ b/doc/manpages/man1/macusers.1.xml @@ -0,0 +1,75 @@ + + + + macusers + + 1 + + 13 Oct 2011 + + :NETATALK_VERSION: + + + + macusers + + List the users connecting via AFP + + + + + macusersmacusers + + + + macusersmacusers + + + -v + -version + --version + -h + -help + --help + + + + + + + DESCRIPTION + + macusers list the users connecting via AFP. + + + + + OPTIONS + + + + + + + Show version and exit + + + + + + + + + Display the help and exit + + + + + + + + SEE ALSO + + afpd8 + + diff --git a/doc/manpages/man1/megatron.1.xml b/doc/manpages/man1/megatron.1.xml new file mode 100644 index 00000000..34c3d0cc --- /dev/null +++ b/doc/manpages/man1/megatron.1.xml @@ -0,0 +1,143 @@ + + + + megatron + + 1 + + 02 Sep 2011 + + :NETATALK_VERSION: + + + + megatron + + unhex + + unbin + + unsingle + + hqx2bin + + single2bin + + macbinary + + Macintosh file format transformer + + + + + megatronmegatron + + sourcefile + + + + unbinunbin + + sourcefile + + + + unhexunhex + + sourcefile + + + + unsingleunsingle + + sourcefile + + + + hqx2binhqx2bin + + sourcefile + + + + single2binsingle2bin + + sourcefile + + + + macbinarymacbinary + + sourcefile + + + + + DESCRIPTION + + megatron is used to transform files from BinHex, + MacBinary, AppleSingle, or netatalk style + AppleDouble formats into MacBinary or netatalk + style AppleDouble formats. The netatalk + style AppleDouble format is the file format used by afpd, + the netatalk Apple Filing Protocol + (AppleShare) server. BinHex, MacBinary, and AppleSingle are commonly used + formats for transferring Macintosh files between machines via email or + file transfer protocols. megatron uses its name to + determine what type of transformation is being asked of it. + + If megatron is called as unhex + , unbin or unsingle, it tries to + convert file(s) from BinHex, MacBinary, or AppleSingle into AppleDouble + format. BinHex is the format most often used to send Macintosh files by + e-mail. Usually these files have an extension of ".hqx". MacBinary + is the format most often used by terminal emulators "on the fly" + when transferring Macintosh files in binary mode. MacBinary files often + have an extension of ".bin". Some Macintosh LAN-based email + packages use uuencoded AppleSingle format to "attach" or + "enclose" files in email. AppleSingle files don't have a + standard filename extension. + + If megatron is called as hqx2bin, + single2bin, or macbinary, it will + try to convert the file(s) from BinHex, AppleSingle, or AppleDouble into + MacBinary. This last translation may be useful in moving Macintosh files + from your afpd server to some other machine when you + can't copy them from the server using a Macintosh for some reason. + + If megatron is called with any other name, it + uses the default translation, namely unhex. + + If no source file is given, or if sourcefile + is `-', and if the + conversion is from a BinHex or MacBinary file, megatron + will read from standard input. + + The filename used to store any output file is the filename that is + encoded in the source file. MacBinary files are created with a + ".bin" extension. In the case of conflicts, the old file is + overwritten! + + + + OPTIONS + + + + + + + Show version. + + + + + + + + + SEE ALSO + + afpd8 + + diff --git a/doc/manpages/man1/netatalk-config.1.xml b/doc/manpages/man1/netatalk-config.1.xml new file mode 100644 index 00000000..391ae5ce --- /dev/null +++ b/doc/manpages/man1/netatalk-config.1.xml @@ -0,0 +1,163 @@ + + + + netatalk-config + + 1 + + 09 June 2001 + + :NETATALK_VERSION: + + The Netatalk Project + + + + netatalk-config + + script to get information about the installed version of + netatalk + + + + + netatalk-confignetatalk-config + + --prefix =DIR + + --exec_prefix =DIR + + --help + + --version + + --libs + + --libs-dirs + + --libs-names + + --cflags + + --macros + + + + + DESCRIPTION + + netatalk-config is a tool that is used to + determine the compiler and linker flags that should be used + to compile and link programs that use the netatalk + run-time libraries. + + + + OPTIONS + + netatalk-config accepts the following options: + + + + + + + Print a short help for this command and exit. + + + + + + + + Print the currently installed version of netatalk + on the standard output. + + + + + + + + Print the linker flags that are necessary to link against the + netatalk run-time libraries. + + + + + + + + Print only the -l/-R part of --libs. + + + + + + + + Print only the -l part of --libs. + + + + + + + + Print the compiler flags that are necessary to compile a + program linked against the netatalk + run-time libraries. + + + + + + + + Print the netatalk m4 + directory. + + + + + + + + If specified, use PREFIX instead of the installation prefix + that netatalk was built with when + computing the output for the --cflags and --libs options. This + option is also used for the exec prefix if --exec-prefix was not + specified. This option must be specified before any --libs or + --cflags options. + + + + + + + + If specified, use PREFIX instead of the installation exec + prefix that netatalk was built with + when computing the output for the --cflags and --libs options. This + option must be specified before any --libs or --cflags options. + + + + + + + COPYRIGHT + + Copyright © 1998 Owen Taylor + + Permission to use, copy, modify, and distribute this software and + its documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that copyright notice and this permission notice appear in supporting + documentation. + + Man page adapted for netatalk-config by Sebastian + Rittau in 2001. + + diff --git a/doc/manpages/man1/uniconv.1.xml b/doc/manpages/man1/uniconv.1.xml new file mode 100644 index 00000000..8f2d84c1 --- /dev/null +++ b/doc/manpages/man1/uniconv.1.xml @@ -0,0 +1,227 @@ + + + + + uniconv + + 1 + + 19 Jan 2013 + + :NETATALK_VERSION: + + + + uniconv + + convert Netatalk volume encoding + + + + + uniconvuniconv + + -ndv + + -c cnidbackend + + -f fromcode + + -t tocode + + -m maccode + + volumepath + + + + + Description + + uniconv converts the volume encoding of + volumepath from the fromcode + to the tocode encoding. + + + + Options + + + + -c + + + CNID backend used on this volume, usually cdb or dbd. Should + match the backend selected with afpd for this volume. If not + specified, the default CNID backend ":DEFAULT_CNID_SCHEME:" is + used + + + + + -d + + + don't HEX encode leading dots (:2e), equivalent to + in afp.conf5 + + + + + -f + + + encoding to convert from, use ASCII for HEX encoded volumes + + + + + -h + + + display help + + + + + -m + + + Macintosh client codepage, required for HEX encoded volumes. + Defaults to "MAC_ROMAN" + + + + + -n + + + "dry run", don't do any real changes + + + + + -t + + + volume encoding to convert to, e.g. UTF8 + + + + + -v + + + verbose output, use twice for maximum logging. + + + + + -V + + + print version and exit + + + + + + + + + WARNING + + Setting the wrong options might render your data unusable!!! Make + sure you know what you are doing. Always backup your data first. + + It is *strongly* recommended to do + a "dry run" first and to check the output for conversion errors. + + afpd8 + should not be running while you change the volume + encoding. Remember to change or + in + afp.conf5 + to the new codepage, before restarting afpd. + + In case of MacChineseTraditional, + MacJapanese or + MacKorean, + uniconv cannot be used. + + USE AT YOUR OWN RISK!!! + + + + Selectable charsets + + Netatalk provides internal support for UTF-8 (pre- and decomposed) + and HEX. If you want to use other charsets, they must be provided by + iconv1 + + uniconv also knows iso-8859.adapted, an old style + 1.x NLS widely used. This is only intended for upgrading old volumes, + afpd8 + cannot handle iso-8859.adapted anymore. + + + + CNID background + + The CNID backends maintains name to ID mappings. If you change a + filename outside afpd(8) (shell, samba), the CNID db, i.e. the DIDNAME + index, gets inconsistent. Netatalk tries to recover from such + inconsistencies as gracefully as possible. The mechanisms to resolve such + inconsistencies may fail sometimes, though, as this is not an easy task to + accomplish. I.e. if several names in the path to the file or directory + have changed, things may go wrong. + + If you change a lot of filenames at once, chances are higher that + the afpds fallback mechanisms fail, i.e. files will be assigned new IDs, + even though the file hasn't changed. uniconv + therefore updates the CNID entry for each file/directory directly after it + changes the name to avoid inconsistencies. The two supported backends for + volumes, dbd and cdb, use the same CNID db format. Therefore, you + could use uniconv with cdb and + afpd with dbd later. + + Warning: There must not be two + processes opening the CNID database using different backends at once! If a + volume is still opened with dbd (cnid_metad/cnid_dbd) and you start + uniconv with cdb, the result will be a corrupted CNID + database, as the two backends use different locking schemes. You might run + into additional problems, e.g. if dbd is compiled with transactions, cdb + will not update the transaction logs. + + In general, it is recommended to use the same backend for + uniconv you are using with + afpd8. + + + + Examples + + convert 1.x CAP encoded volume to UTF-8, clients used MacRoman + codepage, cnidscheme is dbd: + + example% uniconv -c dbd -f ASCII -t UTF8 -m MAC_ROMAN /path/to/share + + convert iso8859-1 volume to UTF-8, cnidscheme is cdb: + + example% uniconv -c cdb -f ISO-8859-1 -t UTF8 -m MAC_ROMAN /path/to/share + + convert 1.x volume using iso8859-1 adapted NLS to HEX encoding: + + example% uniconv -f ISO-8859-ADAPTED -t ASCII -m MAC_ROMAN/path/to/share + + convert UTF-8 volume to HEX, for MacCyrillic clients: + + example% uniconv -f UTF8 -t ASCII -m MAC_CYRILLIC /path/to/share + + + + See also + + afp.conf5,afpd8,iconv1,cnid_metad8,cnid_dbd8 + + diff --git a/doc/manpages/man5/afp.conf.5.xml b/doc/manpages/man5/afp.conf.5.xml new file mode 100644 index 00000000..44b5a468 --- /dev/null +++ b/doc/manpages/man5/afp.conf.5.xml @@ -0,0 +1,1960 @@ + + + + afp.conf + + 5 + + 30 Apr 2013 + + :NETATALK_VERSION: + + + + afp.conf + + Netatalk configuration file + afp.conf + + + + + SYNOPSIS + + The afp.conf file is the configuration file for + the Netatalk AFP file server. + + All AFP specific configuration and AFP volume definitions are done + via this file. + + + + FILE FORMAT + + The file consists of sections and parameters. A section begins with + the name of the section in square brackets and continues until the next + section begins. Sections contain parameters of the form: + name = value + + + The file is line-based - that is, each newline-terminated line + represents either a comment, a section name or a parameter. + + Section and parameter names are case sensitive. + + Only the first equals sign in a parameter is significant. Whitespace + before or after the first equals sign is discarded. Leading, trailing and + internal whitespace in section and parameter names is irrelevant. Leading + and trailing whitespace in a parameter value is discarded. Internal + whitespace within a parameter value is retained verbatim. + + Any line beginning with a semicolon (;) or a hash + (#) character is ignored, as are lines containing only + whitespace. + + Any line ending in a \ is + continued on the next line in the customary UNIX fashion. + + The values following the equals sign in parameters are all either a + string (no quotes needed) or a boolean, which may be given as yes/no, 1/0 + or true/false. Case is not significant in boolean values, but is preserved + in string values. Some items such as create masks are numeric. + + The parameter allows you to include one config + file inside another. The file is included literally, as though typed in + place. Nested includes are not supported. + + + + SECTION DESCRIPTIONS + + Each section in the configuration file (except for the [Global] + section) describes a shared resource (known as a volume). + The section name is the name of the volume and the parameters within the + section define the volume attributes and options. + + There are two special sections, [Global] and [Homes], which are + described under special sections. The following notes + apply to ordinary section descriptions. + + A volume consists of a directory to which access is being given plus + a description of the access rights which are granted to the user of the + service. For volumes the option must specify the + directory to share. + + Any volume section without option is + considered a vol preset which can be selected in + other volume sections via the option and + constitutes defaults for the volume. For any option specified both in a + preset and in a volume section the volume section + setting completely substitutes the preset option. + + The access rights granted by the server are masked by the access + rights granted to the specified or guest UNIX user by the host system. The + server does not grant more access than the host system grants. + + The following sample section defines an AFP volume. The user has + full access to the path /foo/bar. The share is + accessed via the share name baz: [baz] + path = /foo/bar + + + + SPECIAL SECTIONS + + + The [Global] section + + Parameters in this section apply to the server as a whole. + Parameters denoted by a (G) below are must be set in this + section. + + + + The [Homes] section + + This section enable sharing of the UNIX server user home + directories. Specifying an optional parameter + means that not the whole user home will be shared but the subdirectory + . It is necessary to define the option. It should be a regex which matches the parent + directory of the user homes. Parameters denoted by a (H) belong to + volume sections. The optional parameter can + be used to change the AFP volume name which $u's + home by default. See below under VARIABLE + SUBSTITUTIONS. + + The following example illustrates this. Given all user home + directories are stored under /home: + [Homes] + path = afp-data + basedir regex = /home For a user + john this results in an AFP home volume with a path + of /home/john/afp-data. + + If contains symlink, set the + canonicalized absolute path. When /home links to + /usr/home: [Homes] + basedir regex = /usr/home + + + + + PARAMETERS + + Parameters define the specific attributes of sections. + + Some parameters are specific to the [Global] section (e.g., + log type). All others are permissible only in volume + sections. The letter G in parentheses indicates that + a parameter is specific to the [Global] section. The letter + V indicates that a parameter can be specified in a + volume specific section. + + + + VARIABLE SUBSTITUTIONS + + You can use variables in volume names. The use of variables in paths + is not supported for now. + + + + if you specify an unknown variable, it will not get + converted. + + + + if you specify a known variable, but that variable doesn't have + a value, it will get ignored. + + + + The variables which can be used for substitutions are: + + + + $b + + + basename + + + + + $c + + + client's ip address + + + + + $d + + + volume pathname on server + + + + + $f + + + full name (contents of the gecos field in the passwd + file) + + + + + $g + + + group name + + + + + $h + + + hostname + + + + + $i + + + client's ip, without port + + + + + $s + + + server name (this can be the hostname) + + + + + $u + + + user name (if guest, it is the user that guest is running + as) + + + + + $v + + + volume name + + + + + $$ + + + prints dollar sign ($) + + + + + + + EXPLANATION OF GLOBAL PARAMETERS + + + Authentication Options + + + + ad domain = DOMAIN + (G) + + + Append @DOMAIN to username when authenticating. Useful in + Active Directory environments that otherwise would require the + user to enter the full user@domain string. + + + + + admin auth user = user + (G) + + + Specifying eg "" + whenever a normal user login fails, afpd will try to authenticate + as the specified . If this + succeeds, a normal session is created for the original connecting + user. Said differently: if you know the password of , you can authenticate as any other user. + + + + + k5 keytab = path + (G) + + k5 service = service + (G) + + k5 realm = realm + (G) + + + These are required if the server supports the Kerberos 5 + authentication UAM. + + + + + nt domain = DOMAIN + (G) + + nt separator = SEPARATOR + (G) + + + Use for eg. winbind authentication, prepends both strings + before the username from login and then tries to authenticate with + the result through the available and active UAM authentication + modules. + + + + + save password = BOOLEAN (default: + yes) (G) + + + Enables or disables the ability of clients to save passwords + locally. + + + + + set password = BOOLEAN (default: + no) (G) + + + Enables or disables the ability of clients to change their + passwords via chooser or the "connect to server" dialog. + + + + + uam list = uam list + (G) + + + Space or comma separated list of UAMs. (The default is + "uams_dhx.so uams_dhx2.so"). + + The most commonly used UAMs are: + + + + uams_guest.so + + + allows guest logins + + + + + uams_clrtxt.so + + + (uams_pam.so or uams_passwd.so) Allow logins with + passwords transmitted in the clear. (legacy) + + + + + uams_randum.so + + + allows Random Number and Two-Way Random Number + Exchange for authentication (requires a separate file + containing the passwords, either :ETCDIR:/afppasswd file or + the one specified via "". See + + afppasswd + + 1 + for details. (legacy) + + + + + uams_dhx.so + + + (uams_dhx_pam.so or uams_dhx_passwd.so) Allow + Diffie-Hellman eXchange (DHX) for authentication. + + + + + uams_dhx2.so + + + (uams_dhx2_pam.so or uams_dhx2_passwd.so) Allow + Diffie-Hellman eXchange 2 (DHX2) for authentication. + + + + + uam_gss.so + + + Allow Kerberos V for authentication (optional) + + + + + + + + uam path = path + (G) + + + Sets the default path for UAMs for this server (default is + :LIBDIR:/netatalk). + + + + + + + Charset Options + + With OS X Apple introduced the AFP3 protocol. One of the big + changes was, that AFP3 uses Unicode names encoded as Decomposed UTF-8 + (UTF8-MAC). Previous AFP/OS versions used charsets like MacRoman, + MacCentralEurope, etc. + + To be able to serve AFP3 and older clients at the same time, + afpd needs to be able to convert between UTF-8 and + Mac charsets. Even OS X clients partly still rely on the mac charset. As + there's no way, afpd can detect the codepage a pre + AFP3 client uses, you have to specify it using the option. The default is MacRoman, which should be fine + for most western users. + + As afpd needs to interact with UNIX operating + system as well, it need's to be able to convert from UTF8-MAC / Mac + charset to the UNIX charset. By default afpd uses + UTF8. You can set the UNIX charset using the + option. If you're using extended + characters in the configuration files for afpd, make + sure your terminal matches the . + + + + mac charset = CHARSET + (G)/(V) + + + Specifies the Mac clients charset, e.g. + MAC_ROMAN. This is used to convert strings + and filenames to the clients codepage for OS9 and Classic, i.e. + for authentication and AFP messages (SIGUSR2 messaging). This will + also be the default for the volumes . + Defaults to MAC_ROMAN. + + + + + unix charset = CHARSET + (G) + + + Specifies the servers unix charset, e.g. + ISO-8859-15 or EUC-JP. + This is used to convert strings to/from the systems locale, e.g. + for authentication, server messages and volume names. If + LOCALE is set, the systems locale is used. + Defaults to UTF8. + + + + + vol charset = CHARSET + (G)/(V) + + + Specifies the encoding of the volumes filesystem. By + default, it is the same as . + + + + + + + Password Options + + + + passwd file = path + (G) + + + Sets the path to the Randnum UAM passwd file for this server + (default is :ETCDIR:/afppasswd). + + + + + passwd minlen = number + (G) + + + Sets the minimum password length, if supported by the + UAM + + + + + + + Network Options + + + + advertise ssh = BOOLEAN (default: + no) (G) + + + Allows old Mac OS X clients (10.3.3-10.4) to automagically + establish a tunneled AFP connection through SSH. If this option is + set, the server's answers to client's FPGetSrvrInfo requests + contain an additional entry. It depends on both client's settings + and a correctly configured and running + sshd + + 8 + on the server to let things work. + + + Setting this option is not recommended since globally + encrypting AFP connections via SSH will increase the server's + load significantly. On the other hand, Apple's client side + implementation of this feature in MacOS X versions prior to + 10.3.4 contained a security flaw. + + + + + + afp listen = ip address[:port] [ip address[:port] + ...] (G) + + + Specifies the IP address that the server should advertise + and listens to. The default is + advertise the first IP address of the system, but to listen for + any incoming request. The network address may be specified either + in dotted-decimal format for IPv4 or in hexadecimal format for + IPv6. + + + + + afp port = port number + (G) + + + Allows a different TCP port to be used for AFP. The default + is 548. Also sets the default port applied when none specified in + an option. + + + + + cnid listen = ip address[:port] [ip + address[:port] ...] (G) + + + Specifies the IP address that the CNID server should listen + on. The default is localhost:4700. + + + + + disconnect time = number + (G) + + + Keep disconnected AFP sessions for + number hours before dropping them. Default + is 24 hours. + + + + + dsireadbuf = number + (G) + + + Scale factor that determines the size of the DSI/TCP + readahead buffer, default is 12. This is multiplies with the DSI + server quantum (default ~300k) to give the size of the buffer. + Increasing this value might increase throughput in fast local + networks for volume to volume copies. Note: + This buffer is allocated per afpd child process, so specifying + large values will eat up large amount of memory (buffer size * + number of clients). + + + + + fqdn = name:port + (G) + + + Specifies a fully-qualified domain name, with an optional + port. This is discarded if the server cannot resolve it. This + option is not honored by AppleShare clients <= 3.8.3. This + option is disabled by default. Use with caution as this will + involve a second name resolution step on the client side. Also + note that afpd will advertise this name:port combination but not + automatically listen to it. + + + + + hostname = name + (G) + + + Use this instead of the result from calling hostname for + determining which IP address to advertise, therefore the hostname + is resolved to an IP which is the advertised. This is NOT used for + listening and it is also overwritten by . + + + + + max connections = number + (G) + + + Sets the maximum number of clients that can simultaneously + connect to the server (default is 200). + + + + + server quantum = number + (G) + + + This specifies the DSI server quantum. The default value is + 1 MB. The maximum value is 0xFFFFFFFFF, the minimum is 32000. If + you specify a value that is out of range, the default value will + be set. Do not change this value unless you're absolutely sure, + what you're doing + + + + + sleep time = number + (G) + + + Keep sleeping AFP sessions for number + hours before disconnecting clients in sleep mode. Default is 10 + hours. + + + + + tcprcvbuf = number + (G) + + + Try to set TCP receive buffer using setsockpt(). Often OSes + impose restrictions on the applications ability to set this + value. + + + + + tcpsndbuf = number + (G) + + + Try to set TCP send buffer using setsockpt(). Often OSes + impose restrictions on the applications ability to set this + value. + + + + + use sendfile = BOOLEAN (default: + yes) (G) + + + Whether to use sendfile + sendfile + syscall for sending file data to clients. + + + + + zeroconf = BOOLEAN (default: + yes) (G) + + + Whether to use automatic Zeroconf + Zeroconf + + Bonjour + service registration if Avahi or mDNSResponder were + compiled in. + + + + + + + Miscellaneous Options + + + + admin group = group + (G) + + + Allows users of a certain group to be seen as the superuser + when they log in. This option is disabled by default. + + + + + afp read locks = BOOLEAN (default: + no) (G) + + + Whether to apply locks to the byte region read in FPRead + calls. The AFP spec mandates this, but it's not really in line + with UNIX semantics and is a performance hug. + + + + + afpstats = BOOLEAN (default: + no) (G) + + + Whether to provide AFP runtime statistics (connected + users, open volumes) via dbus. + + + + + basedir regex = regex + (H) + + + Regular expression which matches the parent directory of the + user homes. If contains symlink, + you must set the canonicalized absolute path. In the simple case + this is just a path ie + + + + + close vol = BOOLEAN (default: + no) (G) + + + Whether to close volumes possibly opened by clients when + they're removed from the configuration and the configuration is + reloaded. + + + + + cnid server = ipaddress[:port] + (G)/(V) + + + Specifies the IP address and port of a cnid_metad server, + required for CNID dbd backend. Defaults to localhost:4700. The + network address may be specified either in dotted-decimal format + for IPv4 or in hexadecimal format for IPv6.- + + + + + dircachesize = number + (G) + + + Maximum possible entries in the directory cache. The cache + stores directories and files. It is used to cache the full path to + directories and CNIDs which considerably speeds up directory + enumeration. + + Default size is 8192, maximum size is 131072. Given value is + rounded up to nearest power of 2. Each entry takes about 100 + bytes, which is not much, but remember that every afpd child + process for every connected user has its cache. + + + + + extmap file = path + (G) + + + Sets the path to the file which defines file extension + type/creator mappings. (default is :ETCDIR:/extmap.conf). + + + + + guest account = name + (G) + + + Specifies the user that guests should use (default is + "nobody"). The name should be quoted. + + + + + home name = name + (H) + + + AFP user home volume name. The default is user's + home. + + + + + login message = message + (G)/(V) + + + Sets a message to be displayed when clients logon to the + server. The message should be in and + should be quoted. Extended characters are allowed. + + + + + mimic model = model + (G) + + + Specifies the icon model that appears on clients. Defaults + to off. Note that afpd must support Zeroconf. + Examples: RackMac (same as Xserve), PowerBook, PowerMac, + Macmini, iMac, MacBook, MacBookPro, MacBookAir, MacPro, + AppleTV1,1, AirPort. + + + + + signature = <text> (G) + + + Specify a server signature. The maximum length is 16 + characters. This option is useful for clustered environments, to + provide fault isolation etc. By default, afpd generate signature + and saving it to + :STATEDIR:/netatalk/afp_signature.conf + automatically (based on random number). See also + asip-status.pl(1). + + + + + solaris share reservations = + BOOLEAN (default: + yes) (G) + + + Use share reservations on Solaris. Solaris CIFS server uses + this too, so this makes a lock coherent multi protocol + server. + + + + + vol dbpath = path + (G) + + + Sets the database information to be stored in path. You have + to specify a writable location, even if the volume is read only. + The default is + :STATEDIR:/netatalk/CNID/. + + + + + volnamelen = number + (G) + + + Max length of UTF8-MAC volume name for Mac OS X. Note that + Hangul is especially sensitive to this. + + 73: limit of Mac OS X 10.1 80: limit of Mac + OS X 10.4/10.5 (default) 255: limit of recent Mac OS + X Mac OS 9 and earlier are not influenced by + this, because Maccharset volume name is always limited to 27 + bytes. + + + + + vol preset = name + (G)/(V) + + + Use section as option preset for all + volumes (when set in the [Global] section) or for one volume (when + set in that volume's section). + + + + + + + Logging Options + + + + log file = logfile + (G) + + + If not specified Netatalk logs to syslogs daemon facility. + Otherwise it logs to . + + + + + log level = type:level [type:level + ...] (G) + + log level = type:level,[type:level, + ...] (G) + + + Specify that any message of a loglevel up to the given + should be logged. + + By default afpd logs to syslog with a default logging setup + equivalent to + + logtypes: default, afpdaemon, logger, uamsdaemon + + loglevels: severe, error, warn, note, info, debug, debug6, + debug7, debug8, debug9, maxdebug + + + Both logtype and loglevels are case insensitive. + + + + + + + + Filesystem Change Events (FCE<indexterm> + <primary>FCE</primary> + </indexterm>) + + Netatalk includes a nifty filesystem change event mechanism where + afpd processes notify interested listeners about certain filesystem + event by UDP network datagrams. + + + + fce listener = host[:port] + (G) + + + Enables sending FCE events to the specified + host, default port + is 12250 if not specified. Specifying multiple listeners is done + by having this option once for each of them. + + + + + fce events = + fmod,fdel,ddel,fcre,dcre,tmsz + (G) + + + Specifies which FCE events are active, default is + fmod,fdel,ddel,fcre,dcre. + + + + + fce coalesce = all|delete|create + (G) + + + Coalesce FCE events. + + + + + fce holdfmod = seconds + (G) + + + This determines the time delay in seconds which is always + waited if another file modification for the same file is done by a + client before sending an FCE file modification event (fmod). For + example saving a file in Photoshop would generate multiple events + by itself because the application is opening, modifying and + closing a file multiple times for every "save". Default: 60 + seconds. + + + + + + + Debug Parameters + + These options are useful for debugging only. + + + + tickleval = number + (G) + + + Sets the tickle timeout interval (in seconds). Defaults to + 30. + + + + + timeout = number + (G) + + + Specify the number of tickles to send before timing out a + connection. The default is 4, therefore a connection will timeout + after 2 minutes. + + + + + client polling = BOOLEAN (default: + no) (G) + + + With this option enabled, afpd won't advertise that it is + capable of server notifications, so that connected clients poll + the server every 10 seconds to detect changes in opened server + windows. Note: Depending on the number of + simultaneously connected clients and the network's speed, this can + lead to a significant higher load on your network! + + Do not use this option any longer as present Netatalk + correctly supports server notifications, allowing connected + clients to update folder listings in case another client changed + the contents. + + + + + + + Options for ACL handling + + By default, the effective permission of the authenticated user are + only mapped to the mentioned UARights permission structure, not the UNIX + mode. You can adjust this behaviour with the configuration option + : + + + + map acls = none|rights|mode + (G) + + + + + none + + + no mapping of ACLs + + + + + rights + + + effective permissions are mapped to UARights + structure. This is the default. + + + + + mode + + + ACLs are additionally mapped to the UNIX mode of the + filesystem object. + + + + + + + + If you want to be able to display ACLs on the client, you must + setup both client and server as part on a authentication domain + (directory service, eg LDAP, Open Directory, Active Directory). The + reason is, in OS X ACLs are bound to UUIDs, not just uid's or gid's. + Therefor Netatalk must be able to map every filesystem uid and gid to a + UUID so that it can return the server side ACLs which are bound to UNIX + uid and gid mapped to OS X UUIDs. + + Netatalk can query a directory server using LDAP queries. Either + the directory server already provides an UUID attribute for user and + groups (Active Directory, Open Directory) or you reuse an unused + attribute (or add a new one) to you directory server (eg + OpenLDAP). + + The following LDAP options must be configured for Netatalk: + + + + ldap auth method = none|simple|sasl + (G) + + + Authentication method: + + + + none + + + anonymous LDAP bind + + + + + simple + + + simple LDAP bind + + + + + sasl + + + SASL. Not yet supported ! + + + + + + + + ldap auth dn = dn + (G) + + + Distinguished Name of the user for simple bind. + + + + + ldap auth pw = password + (G) + + + Distinguished Name of the user for simple bind. + + + + + ldap server = host + (G) + + + Name or IP address of your LDAP Server. This is only needed + for explicit ACL support in order to be able to query LDAP for + UUIDs. + + You can use + afpldaptest + + 1 + to syntactically check your config. + + + + + ldap userbase = base dn + (G) + + + DN of the user container in LDAP. + + + + + ldap userscope = scope + (G) + + + Search scope for user search: + + + + + ldap groupbase = base dn + (G) + + + DN of the group container in LDAP. + + + + + ldap groupscope = scope + (G) + + + Search scope for user search: + + + + + ldap uuid attr = dn + (G) + + + Name of the LDAP attribute with the UUIDs. + + Note: this is used both for users and groups. + + + + + ldap name attr = dn + (G) + + + Name of the LDAP attribute with the users short name. + + + + + ldap uuid string = STRING + (G) + + + Format of the uuid string in the directory. A series of x + and -, where every x denotes a value 0-9a-f and every - is a + separator. + + Default: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + + + + + ldap uuid encoding = string | ms-guid (default: + string) (G) + + + Format of the UUID of the LDAP attribute, allows usage of + the binary objectGUID fields from Active Directory. If left + unspecified, string is the default, which passes through the ASCII + UUID returned by most other LDAP stores. If set to ms-guid, the + internal UUID representation is converted to and from the binary + format used in the objectGUID attribute found on objects in Active + Directory when interacting with the server. + + + + string + + + UUID is a string, use with eg OpenDirectory. + + + + + ms-guid + + + Binary objectGUID from Active Directory + + + + + + + + ldap group attr = dn + (G) + + + Name of the LDAP attribute with the groups short + name. + + + + + + + + EXPLANATION OF VOLUME PARAMETERS + + + Parameters + + The section name defines the volume name. + No two volumes may have the same + name. The volume name cannot contain the ':' + character. The volume name is mangled if it is very long. Mac charset + volume name is limited to 27 characters. UTF8-MAC volume name is limited + to volnamelen parameter. + + + + path = PATH (V) + + + The path name must be a fully qualified path name. + + + + + appledouble = ea|v2 + (V) + + + Specify the format of the metadata files, which are used for + saving Mac resource fork as well. Earlier versions used + AppleDouble v2, the new default format is ea. + + + + + vol size limit = size in MiB + (V) + + + Useful for Time Machine: limits the reported volume size, + thus preventing Time Machine from using the whole real disk space + for backup. Example: "vol size limit = 1000" would limit the + reported disk space to 1 GB. IMPORTANT: + This is an approximated calculation taking into + account the contents of Time Machine sparsebundle images. Therefor + you MUST NOT use this volume to store other content when using + this option, because it would NOT be accounted. The calculation + works by reading the band size from the Info.plist XML file of the + sparsebundle, reading the bands/ directory counting the number of + band files, and then multiplying one with the other. + + + + + valid users = user @group + (V) + + + The allow option allows the users and groups that access a + share to be specified. Users and groups are specified, delimited + by spaces or commas. Groups are designated by a @ prefix. Names + may be quoted in order to allow for spaces in names. Example: + valid users = user "user 2" @group “@group 2" + + + + + invalid users = users/groups + (V) + + + The deny option specifies users and groups who are not + allowed access to the share. It follows the same format as the + "valid users" option. + + + + + hosts allow = IP host address/IP netmask bits [ + ... ] (V) + + + Only listed hosts and networks are allowed, all others are + rejected. The network address may be specified either in + dotted-decimal format for IPv4 or in hexadecimal format for + IPv6. + + Example: hosts allow = 10.1.0.0/16 10.2.1.100 + 2001:0db8:1234::/48 + + + + + hosts deny = IP host address/IP netmask bits [ + ... ] (V) + + + Listed hosts and nets are rejected, all others are + allowed. + + Example: hosts deny = 192.168.100/24 10.1.1.1 + 2001:db8::1428:57ab + + + + + cnid scheme = backend + (V) + + + set the CNID backend to be used for the volume, default is + [:DEFAULT_CNID_SCHEME:] available schemes: + [:COMPILED_BACKENDS:] + + + + + ea = none|auto|sys|ad + (V) + + + Specify how Extended Attributes + Extended Attributes + are stored. is the + default. + + + + auto + + + Try (by setting an EA on the + shared directory itself), fallback to . + Requires writable volume for performing test. "" overwrites with + . Use explicit "" for read-only volumes where + appropriate. + + + + + sys + + + Use filesystem Extended Attributes. + + + + + ad + + + Use files in .AppleDouble + directories. + + + + + none + + + No Extended Attributes support. + + + + + + + + mac charset = CHARSET + (V) + + + specifies the Mac client charset for this Volume, e.g. + MAC_ROMAN, MAC_CYRILLIC. + If not specified the global setting is applied. This setting is + only required if you need volumes, where the Mac charset differs + from the one globally set in the [Global] section. + + + + + casefold = (V) + + + The casefold option handles, if the case of filenames should + be changed. The available options are: + + - Lowercases names in both + directions. + + - Uppercases names in both + directions. + + - Client sees lowercase, server + sees uppercase. + + - Client sees uppercase, server + sees lowercase. + + + + + password = password + (V) + + + This option allows you to set a volume password, which can + be a maximum of 8 characters long (using ASCII strongly + recommended at the time of this writing). + + + + + file perm = mode + (V) + + directory perm = mode + (V) + + + Add(or) with the client requested permissions: is for files only, + is for directories only. Don't use with "". + + + Volume for a collaborative workgroup + + file perm = 0660 directory perm = + 0770 + + + + + + umask = mode + (V) + + + set perm mask. Don't use with "". + + + + + preexec = command + (V) + + + command to be run when the volume is mounted, ignored for + user defined volumes + + + + + postexec = command + (V) + + + command to be run when the volume is closed, ignored for + user defined volumes + + + + + root preexec = command + (V) + + + command to be run as root when the volume is mounted, + ignored for user defined volumes + + + + + root postexec = command + (V) + + + command to be run as root when the volume is closed, ignored + for user defined volumes + + + + + rolist = (V) + + + Allows certain users and groups to have read-only access to + a share. This follows the allow option format. + + + + + rwlist = users/groups + (V) + + + Allows certain users and groups to have read/write access to + a share. This follows the allow option format. + + + + + veto files = vetoed names + (V) + + + hide files and directories,where the path matches one of the + '/' delimited vetoed names. The veto string must always be + terminated with a '/', eg. "veto1/", "veto1/veto2/". + + + + + + + Volume options + + Boolean volume options. + + + + acls = BOOLEAN (default: + yes) (V) + + + Whether to flag volumes as supporting ACLs. If ACL support + is compiled in, this is yes by default. + + + + + cnid dev = BOOLEAN (default: + yes) (V) + + + Whether to use the device number in the CNID backends. Helps + when the device number is not constant across a reboot, eg + cluster, ... + + + + + convert appledouble = BOOLEAN + (default: yes) (V) + + + Whether automatic conversion from to is performed when + accessing filesystems from clients. This is generally useful, but + costs some performance. It's recommendable to run + dbd on volumes and do the conversion with that. + Then this option can be set to no. + + + + + follow symlinks = BOOLEAN (default: + no) (V) + + + The default setting is false thus symlinks are not followed + on the server. This is the same behaviour as OS X's AFP server. + Setting the option to true causes afpd to follow symlinks on the + server. symlinks may point outside of the AFP volume, currently + afpd doesn't do any checks for "wide symlinks". + + + + + invisible dots = BOOLEAN (default: + no) (V) + + + make dot files invisible. WARNING: enabling this option will + lead to unwanted sideeffects were OS X applications when saving + files to a temporary file starting with a dot first, then renaming + the temp file to its final name, result in the saved file being + invisible. The only thing this option is useful for is making + files that start with a dot invisible on Mac OS 9. It's + completely useless on Mac OS X, as both in Finder and in Terminal + files starting with a dot are hidden anyway. + + + + + network ids = BOOLEAN (default: + yes) (V) + + + Whether the server support network ids. Setting this to + no will result in the client not using ACL + AFP functions. + + + + + preexec close = BOOLEAN (default: + no) (V) + + + A non-zero return code from preexec close the volume being + immediately, preventing clients to mount/see the volume in + question. + + + + + read only = BOOLEAN (default: + no) (V) + + + Specifies the share as being read only for all users. + Overwrites with + + + + + root preexec close= BOOLEAN + (default: no) (V) + + + A non-zero return code from root_preexec closes the volume + immediately, preventing clients to mount/see the volume in + question. + + + + + search db = BOOLEAN (default: + no) (V) + + + Use fast CNID database namesearch instead of slow recursive + filesystem search. Relies on a consistent CNID database, ie Samba + or local filesystem access lead to inaccurate or wrong results. + Works only for "dbd" CNID db volumes. + + + + + stat vol = BOOLEAN (default: + yes) (V) + + + Whether to stat volume path when enumerating volumes list, + useful for automounting or volumes created by a preexec + script. + + + + + time machine = BOOLEAN (default: + no) (V) + + + Whether to enable Time Machine support for this + volume. + + + + + unix priv = BOOLEAN (default: + yes) (V) + + + Whether to use AFP3 UNIX privileges. This should be set for + OS X clients. See also: , + and . + + + + + + + + CNID backends + + The AFP protocol mostly refers to files and directories by ID and + not by name. Netatalk needs a way to store these ID's in a persistent way, + to achieve this several different CNID backends are available. The CNID + Databases are by default located in the + :STATEDIR:/netatalk/CNID/(volumename)/.AppleDB/ + directory. + + + + cdb + + + "Concurrent database", backend is based on Oracle Berkley DB. + With this backend several afpd daemons access the + CNID database directly. Berkeley DB locking is used to synchronize + access, if more than one afpd process is active + for a volume. The drawback is, that the crash of a single + afpd process might corrupt the database. + + + + + dbd + + + Access to the CNID database is restricted to the + cnid_metad daemon process. + afpd processes communicate with the daemon for + database reads and updates. If built with Berkeley DB transactions + the probability for database corruption is practically zero, but + performance can be slower than with + + + + + last + + + This backend is an exception, in terms of ID persistency. ID's + are only valid for the current session. This is basically what + afpd did in the 1.5 (and 1.6) versions. This + backend is still available, as it is useful for e.g. sharing cdroms. + Starting with Netatalk 3.0, it becomes the read only + mode automatically. + + Warning: It is + NOT recommended to use this backend for volumes + anymore, as afpd now relies heavily on a + persistent ID database. Aliases will likely not work and filename + mangling is not supported. + + + + + Even though ./configure --help might show that + there are other CNID backends available, be warned those are likely broken + or mainly used for testing. Don't use them unless you know what you're + doing, they may be removed without further notice from future + versions. + + + + Charset options + + With OS X Apple introduced the AFP3 protocol. One of the most + important changes was that AFP3 uses unicode names encoded as UTF-8 + decomposed. Previous AFP/OS versions used codepages, like MacRoman, + MacCentralEurope, etc. + + afpd needs a way to preserve extended Macintosh + characters, or characters illegal in unix filenames, when saving files on + a unix filesystem. Earlier versions used the the so called CAP encoding. + An extended character (>0x7F) would be converted to a :xx sequence, + e.g. the Apple Logo (MacRoman: 0xF0) was saved as :f0. + Some special characters will be converted as to :xx notation as well. + '/' will be encoded to :2f, if + is not specified, a leading dot + '.' will be encoded as :2e. + + This version now uses UTF-8 as the default encoding for names. + '/' will be converted to ':'. + + The option will allow you to select + another volume encoding. E.g. for western users another useful setting + could be vol charset ISO-8859-15. afpd will accept any + + iconv + + 1 + provided charset. If a character cannot be converted + from the to the selected , afpd will save it as a CAP encoded character. For AFP3 + clients, afpd will convert the UTF-8 + UTF8 + + afpd's vol charset setting + + UTF8-MAC + + afpd's vol charset setting + + ISO-8859-15 + + afpd's vol charset setting + + ISO-8859-1 + + afpd's vol charset setting + character to first. If this + conversion fails, you'll receive a -50 error on the mac. + + Note: Whenever you can, please stick with the + default UTF-8 volume format. + + + + SEE ALSO + + + afpd + + 8 + , + afppasswd + + 5 + , + afp_signature.conf + + 5 + , + extmap.conf + + 5 + , + cnid_metad + + 8 + + + diff --git a/doc/manpages/man5/afp_signature.conf.5.xml b/doc/manpages/man5/afp_signature.conf.5.xml new file mode 100644 index 00000000..a5ed793a --- /dev/null +++ b/doc/manpages/man5/afp_signature.conf.5.xml @@ -0,0 +1,87 @@ + + + + + afp_signature.conf + + 5 + + 23 Mar 2012 + + :NETATALK_VERSION: + + + + afp_signature.conf + + Configuration file used by afpd(8) to specify server + signature + afp_signature.conf + + + + + Description + + :STATEDIR:/netatalk/afp_signature.conf is the + configuration file used by afpd to specify + server signature automagically. The configuration lines are + composed like: + + "server name" + hexa-string + + The first field is server name. Server names must be quoted + if they contain spaces. The second field is the hexadecimal string + of 32 characters for 16-bytes server signature. + The leading spaces and tabs are ignored. Blank lines are ignored. + The lines prefixed with # are ignored. The illegal lines are ignored. + + + + Server Signature is unique 16-bytes identifier used to + prevent logging on to the same server twice. + Netatalk 2.0 and earlier generated server signature by using + gethostid(). There was a problem that another servers have the same + signature because the hostid is not unique enough. + Current netatalk generates the signature from random numbers and + saves it into afp_signature.conf. When starting next time, it + is read from this file. + This file should not be thoughtlessly edited and be copied + onto another server. If it wants to set the signature intentionally, + use the option "signature =" in afp.conf. In this case, + afp_signature.conf is not used. + + + + + + + Examples + + + afp_signature.conf + + # This is a comment. +"My Server" 74A0BB94EC8C13988B2E75042347E528 + + + + + See also + + + afpd + + 8 + , + afp.conf + + 5 + , + asip-status.pl + + 1 + + + diff --git a/doc/manpages/man5/afp_voluuid.conf.5.xml b/doc/manpages/man5/afp_voluuid.conf.5.xml new file mode 100644 index 00000000..b91614e0 --- /dev/null +++ b/doc/manpages/man5/afp_voluuid.conf.5.xml @@ -0,0 +1,88 @@ + + + + + afp_voluuid.conf + + 5 + + 23 Mar 2012 + + :NETATALK_VERSION: + + + + afp_voluuid.conf + + Configuration file used by afpd(8) to specify UUID + for Time Machine volume + afp_voluuid.conf + + + + + Description + + :STATEDIR:/netatalk/afp_voluuid.conf is the + configuration file used by afpd to specify + UUID of Time Machine volume automagically. The configuration + lines are composed like: + + "volume name" + uuid-string + + The first field is volume name. Volume names must be quoted + if they contain spaces. The second field is the 36 character + hexadecimal ASCII string representation of a UUID. + The leading spaces and tabs are ignored. Blank lines are ignored. + The lines prefixed with # are ignored. The illegal lines are ignored. + + + + This UUID is advertised by Zeroconf in order to provide + robust disambiguation of Time Machine volume. + The afpd generates the UUID from random numbers and saves it + into afp_voluuid.conf, only when setting "time machine = yes" option + in afp.conf. + This file should not be thoughtlessly edited and be copied + onto another server. + + + + + + + Examples + + + afp_voluuid.conf three TM volumes on one netatalk + + # This is a comment. +"Backup for John Smith" 1573974F-0ABD-69CC-C40A-8519B681A0E1 +"bob" 39A487F4-55AA-8240-E584-69AA01800FE9 +mary 6331E2D1-446C-B68C-3066-D685AADBE911 + + + + + See also + + + afpd + + 8 + , + afp.conf + + 5 + , + avahi-daemon + + 8 + , + mDNSResponder + + 8 + + + diff --git a/doc/manpages/man5/extmap.conf.5.xml b/doc/manpages/man5/extmap.conf.5.xml new file mode 100644 index 00000000..f9802f32 --- /dev/null +++ b/doc/manpages/man5/extmap.conf.5.xml @@ -0,0 +1,79 @@ + + + + extmap.conf + + 5 + + 19 Jan 2013 + + :NETATALK_VERSION: + + + + extmap.conf + + Configuration file used by afpd(8) to + specify file name extension mappings. + extmap.conf + + + + + + + :ETCDIR:/extmap.confextmap.conf + + + + + Description + + + :ETCDIR:/extmap.conf is the + configuration file used by afpd to + specify file name extension mappings. + + The configuration lines are composed like: + + .extension [ type [ + creator ] ] + + Any line beginning with a hash (“#”) character is ignored. + The leading-dot lines specify file name extension mappings. + The extension '.' sets the default creator and type for otherwise + untyped Unix files. + + + + + Examples + + + Extension is jpg. Type is "JPEG". Creator is "ogle". + + .jpg "JPEG" "ogle" + + + + Extension is lzh. Type is "LHA ". Creator is not defined. + + .lzh "LHA " + + + + + + See Also + + + afp.conf + + 5 + , + afpd + + 8 + + + diff --git a/doc/manpages/man8/afpd.8.xml b/doc/manpages/man8/afpd.8.xml new file mode 100644 index 00000000..ee425fe1 --- /dev/null +++ b/doc/manpages/man8/afpd.8.xml @@ -0,0 +1,259 @@ + + + + afpd + + 8 + + 19 Jan 2013 + + :NETATALK_VERSION: + + + + afpd + + Apple Filing Protocol daemon + + + + + afpd + afpd + + + -d + + -F configfile + + + + afpd + afpd + + + + -v + -V + -h + + + + + + + Description + + afpd provides an Apple Filing Protocol (AFP) + interface to the Unix file system. It is normally started at boot time + by netatalk(8). + + :ETCDIR:/afp.conf is the configuration file + used by afpd to determine the behavior and + configuration of a file server. + + + + + Options + + + + -d + + + Specifies that the daemon should not fork. + + + + + + -v + + + Print version information and exit. + + + + + -V + + + Print verbose information and exit. + + + + + -h + + + Print help and exit. + + + + + -F configfile + + + Specifies the configuration file to use. (Defaults to + :ETCDIR:/afp.conf.) + + + + + + + + SIGNALS + + To shut down a user's afpd process it is + recommended that SIGKILL (-9) + NOT be used, except as a last resort, as this + may leave the CNID database in an inconsistent state. The safe way + to terminate an afpd is to send it a + SIGTERM (-15) signal and wait for it to die on + its own. + SIGTERM and SIGUSR1 signals that are sent to the main afpd process + are propagated to the children, so all will be affected. + + + + SIGTERM + + Clean exit. Propagates from master to childs. + + + + + SIGQUIT + + Send this to the master afpd, it will + exit leaving all children running! Can be used to implement + AFP service without downtime. + + + + + SIGHUP + + Sending a SIGHUP to afpd will cause it to + reload its configuration files. + + + + + SIGINT + + Sending a SIGINT to a child + afpd enables max_debug + logging for this process. The log is sent to the file + /tmp/afpd.PID.XXXXXX. Sending another + SIGINT will revert to the original log settings. + + + + + SIGUSR1 + + The afpd process will send the message "The + server is going down for maintenance." to the client and shut itself + down in 5 minutes. New connections are not allowed. If this is sent + to a child afpd, the other children are not affected. However, the + main process will still exit, disabling all new connections. + + + + + SIGUSR2 + + The afpd process will look in the message + directory configured at build time for a file named message.pid. For + each one found, a the contents will be sent as a message to the + associated AFP client. The file is removed after the message is + sent. This should only be sent to a child + afpd. + + + + + + + FILES + + + + :ETCDIR:/afp.conf + + + configuration file used by afpd + + + + + :STATEDIR:/netatalk/afp_signature.conf + + + list of server signature + + + + + :STATEDIR:/netatalk/afp_voluuid.conf + + + list of UUID for Time Machine volume + + + + + :ETCDIR:/extmap.conf + + + file name extension mapping + + + + + :ETCDIR:/msg/message.pid + + + contains messages to be sent to users. + + + + + + + SEE ALSO + + + netatalk + + 8 + , + hosts_access + + 5 + , + afp.conf + + 5 + , + afp_signature.conf + + 5 + , + afp_voluuid.conf + + 5 + , + extmap.conf + + 5 + , + dbd + + 1 + . + + diff --git a/doc/manpages/man8/cnid_dbd.8.xml b/doc/manpages/man8/cnid_dbd.8.xml new file mode 100644 index 00000000..89e87945 --- /dev/null +++ b/doc/manpages/man8/cnid_dbd.8.xml @@ -0,0 +1,270 @@ + + + + cnid_dbd + + 8 + + 01 Jan 2012 + + :NETATALK_VERSION: + + + + cnid_dbd + + implement access to CNID databases through a dedicated daemon + process + + + + + cnid_dbd + cnid_dbd + + CNID backend + + NFS + + Network File System + + + + + cnid_dbd + cnid_dbd + + + + -v + -V + + + + + + DESCRIPTION + + cnid_dbd provides an interface for storage and + retrieval of catalog node IDs (CNIDs) and related information to the + afpd daemon. CNIDs are a component of + Macintosh based file systems with semantics that map not easily onto Unix + file systems. This makes separate storage in a database necessary. + cnid_dbd is part of the CNID + backend framework of afpd and + implements the dbd backend. + + cnid_dbd is never started via the command line or + system startup scripts but only by the cnid_metad daemon. There is one instance of + cnid_dbd per netatalk volume. + + cnid_dbd uses the Berkeley + DB database library and uses transactionally protected updates. + The dbd backend with transactions will + avoid corruption of the CNID database even if the system crashes + unexpectedly. + + cnid_dbd inherits the effective userid and + groupid from cnid_metad on startup, which + is normally caused by afpd serving a + netatalk volume to a client. It changes to the Berkeley DB database home directory dbdir that is associated with the volume. If the + userid inherited from cnid_metad is 0 + (root), cnid_dbd will change userid and groupid to the + owner and group of the database home directory. Otherwise, it will + continue to use the inherited values. cnid_dbd will + then attempt to open the database and start serving requests using + filedescriptor clntfd. Subsequent instances + of afpd that want to access the same volume + are redirected to the running cnid_dbd process by + cnid_metad via the filedescriptor ctrlfd. + + cnid_dbd can be configured to run forever or to + exit after a period of inactivity. If cnid_dbd receives + a TERM or an INT signal it will exit cleanly after flushing dirty database + buffers to disk and closing Berkeley DB + database environments. It is safe to terminate cnid_dbd + this way, it will be restarted when necessary. Other signals are not + handled and will cause an immediate exit, possibly leaving the CNID + database in an inconsistent state (no transactions) or losing recent + updates during recovery (transactions). + + The Berkeley DB database subsystem + will create files named log.xxxxxxxxxx in the database home directory + dbdir, where xxxxxxxxxx is a monotonically + increasing integer. These files contain the transactional database + changes. They will be removed regularly, unless the logfile_autoremove option is specified in the + db_param configuration file (see + below) with a value of 0 (default 1). + + + + OPTIONS + + + + + + + Show version and exit. + + + + + + + CONFIGURATION + + cnid_dbd reads configuration information from the + file db_param in the database directory + dbdir on startup. If the file does not + exist or a parameter is not listed, suitable default values are used. The + format for a single parameter is the parameter name, followed by one or + more spaces, followed by the parameter value, followed by a newline. The + following parameters are currently recognized: + + + + logfile_autoremove + + + If set to 0, unused Berkeley DB transactional logfiles + (log.xxxxxxxxxx in the database home directory) are not removed on + startup of cnid_dbd and on a regular basis. + Default: 1. + + + + + cachesize + + + Determines the size of the Berkeley DB cache in kilobytes. + Default: 8192. Each cnid_dbd process grabs that + much memory on top of its normal memory footprint. It can be used to + tune database performance. The db_stat utility with the + option that comes with Berkley DB can help you determine ether you + need to change this value. The default is pretty conservative so + that a large percentage of requests should be satisfied from the + cache directly. If memory is not a bottleneck on your system you + might want to leave it at that value. The Berkeley DB Tutorial and Reference Guide has a + section Selecting a cache size that + gives more detailed information. + + + + + flush_frequency + + flush_interval + + + flush_frequency (Default: 1000) + and flush_interval (Default: 1800) + control how often changes to the database are checkpointed. Both of + these operations are performed if either i) more than flush_frequency requests have been received or + ii) more than flush_interval seconds + have elapsed since the last save/checkpoint. Be careful to check + your harddisk configuration for on disk cache settings. Many IDE + disks just cache writes as the default behaviour, so even flushing + database files to disk will not have the desired effect. + + + + + fd_table_size + + + is the maximum number of connections (filedescriptors) that + can be open for afpd client processes + in cnid_dbd. Default: 512. If this + number is exceeded, one of the existing connections is closed and + reused. The affected afpd process + will transparently reconnect later, which causes slight overhead. On + the other hand, setting this parameter too high could affect + performance in cnid_dbd since all descriptors + have to be checked in a select() system call, + or worse, you might exceed the per process limit of open file + descriptors on your system. It is safe to set the value to 1 on + volumes where only one afpd client + process is expected to run, e.g. home directories. + + + + + idle_timeout + + + is the number of seconds of inactivity before an idle + cnid_dbd exits. Default: 600. Set this to 0 to + disable the timeout. + + + + + + + UPDATING + + Note that the first version to appear after + Netatalk 2.1 ie Netatalk 2.1.1, will support BerkeleyDB updates on the fly + without manual intervention. In other words Netatalk 2.1 does contain code + to prepare the BerkeleyDB database for upgrades and to upgrade it in case + it has been prepared before. That means it can't upgrade a 2.0.x version + because that one didn't prepare the database. + + In order to update between older Netatalk releases using different + BerkeleyDB library versions, follow this steps: + + + + Stop the to be upgraded old version of Netatalk + + + + Using the old BerkeleyDB utilities run db_recover -h + <path to .AppleDB> + + + + Using the new BerkeleyDB utilities run db_upgrade -v -h + <path to .AppleDB> -f cnid2.db + + + + Again using the new BerkeleyDB utilities run + db_checkpoint -1 -h <path to .AppleDB> + + + + Start the the new version of Netatalk + + + + + + + SEE ALSO + + + cnid_metad + + 8 + , + afpd + + 8 + , + dbd + + 1 + + + diff --git a/doc/manpages/man8/cnid_metad.8.xml b/doc/manpages/man8/cnid_metad.8.xml new file mode 100644 index 00000000..587694bb --- /dev/null +++ b/doc/manpages/man8/cnid_metad.8.xml @@ -0,0 +1,129 @@ + + + + cnid_metad + + 8 + + 23 Mar 2012 + + :NETATALK_VERSION: + + + + cnid_metad + + start cnid_dbd daemons on request + + + + + cnid_metad + cnid_metad + + + -d + + -F configuration file + + + + cnid_metad + cnid_metad + + + + -v + -V + + + + + + DESCRIPTION + + cnid_metad waits for requests from afpd to start up instances of the cnid_dbd daemon. It keeps track of the status of a + cnid_dbd instance once started and will + restart it if necessary. cnid_metad is normally started + at boot time by netatalk(8) and runs + until shutdown. + + + + OPTIONS + + + + + + + + cnid_metad will remain in the foreground + and will also leave the standard input, standard output + and standard error file descriptors open. Useful for + debugging. + + + + + configuration file + + + Use configuration file as the + configuration file. The default is + :ETCDIR:/afp.conf. + + + + + + + + Show version and exit. + + + + + + + CAVEATS + + cnid_metad does not block or catch any signals + apart from SIGPIPE. It will therefore exit on most signals received. This + will also cause all instances of cnid_dbd's + started by that cnid_metad to exit gracefully. Since + state about and IPC access to the subprocesses is only maintained in + memory by cnid_metad this is desired behaviour. As soon + as cnid_metad is restarted afpd processes will transparently reconnect. + + + + SEE ALSO + + + netatalk + + 8 + , + cnid_dbd + + 8 + , + afpd + + 8 + , + dbd + + 1 + , + afp.conf + + 5 + + + diff --git a/doc/manpages/man8/netatalk.8.xml b/doc/manpages/man8/netatalk.8.xml new file mode 100644 index 00000000..e9496da6 --- /dev/null +++ b/doc/manpages/man8/netatalk.8.xml @@ -0,0 +1,96 @@ + + + + netatalk + + 8 + + 22 Mar 2012 + + :NETATALK_VERSION: + + + + netatalk + + Netatalk AFP server service controller daemon + + + + + netatalk + netatalk + + + + + netatalk + netatalk + + + + + + Description + + netatalk is the service controller daemon + responsible for starting and restarting the AFP daemon + afpd and the CNID daemon cnid_metad. + It is normally started at boot time from /etc/rc. + + + + SIGNALS + + + + SIGTERM + + + Stop Netatalk service, AFP and CNID daemons + + + + + SIGHUP + + + Sending a SIGHUP will cause the AFP daemon + reload its configuration file. + + + + + + + FILES + + + + :ETCDIR:/afp.conf + + + configuration file used by afpd and cnid_metad + + + + + + + SEE ALSO + + + afpd + + 8 + , + cnid_metad + + 8 + , + afp.conf + + 5 + . + + diff --git a/doc/manual/configuration.xml b/doc/manual/configuration.xml new file mode 100644 index 00000000..3bf6e0ff --- /dev/null +++ b/doc/manual/configuration.xml @@ -0,0 +1,1554 @@ + + + Setting up Netatalk + + + File Services<indexterm> + <primary>File Services</primary> + + <secondary>Netatalk's File Services</secondary> + </indexterm> + + Netatalk supplies AFP + AFP + + Apple Filing Protocol + services. + + + Setting up the AFP file server + + AFP (the Apple Filing Protocol) is the protocol Apple Macintoshes + use for file services. The protocol has evolved over the years. The + latest changes to the protocol, called "AFP 3.3", were added with the + release of Snow Leopard + Snow Leopard + + Mac OS X 10.6 + (Mac OS X 10.6). + + The afpd daemon offers the fileservices to Apple clients. The only + configuration file is afp.conf. It uses a ini style + configuration syntax. + + Mac OS X 10.5 (Leopard) added support for Time Machine backups + over AFP. Two new functions ensure that backups are written to spinning + disk, not just in the server's cache. Different host operating systems + honour this cache flushing differently. To make a volume a Time Machine + target use the volume option "". + + Starting with Netatalk 2.1 UNIX symlinks + symlink + + UNIX symlink + can be used on the server. Semantics are the same as for + eg NFS, ie they are not resolved on the server side but instead it's + completely up to the client to resolve them, resulting in links that + point somewhere inside the clients filesystem view. + + + afp.conf + + afp.conf is the configuration file used by + afpd to determine the behaviour and configuration of the AFP file + serverand the AFP volume that it provides. + + The afp.conf is divided into several + sections: + + [Global] + + + The global section defines general server options + + + + + [Homes] + + + The homes section defines user home volumes + + + Any section not called or + is interpreted as an AFP volume. + + For sharing user homes by defining a + section you must specify the option + which can be a simple string with the path to the parent directory of + all user homes or a regular expression. + + Example: + + [Homes] +basedir regex = /home + + + Now any user logging into the AFP server will have a user volume + available whos path is /home/NAME. + + A more complex setup would be a server with a large amount of + user homes which are split across eg two different + filesystems: + + /RAID1/homes + + + + /RAID2/morehomes + + The following configuration is + required:[Homes] +basedir regex = /RAID./.*homes + + + If contains symlink, set the + canonicalized absolute path. When /home links to + /usr/home: [Homes] +basedir regex = /usr/home + + For a more detailed explanation of the available options, please + refer to the + afp.conf + + 5 + man page. + + + + + CNID<indexterm> + <primary>CNID</primary> + + <secondary>Catalog Node ID</secondary> + </indexterm> backends<indexterm> + <primary>Backend</primary> + + <secondary>CNID backend</secondary> + </indexterm> + + Unlike other protocols like SMB or NFS, the AFP protocol mostly + refers to files and directories by ID and not by a path (the IDs are + also called CNID, that means Catalog Node ID). A typical AFP request + uses a directory ID + DID + + Directory ID + and a filename, something like "server, please + open the file named 'Test' in the directory with id 167". For + example "Aliases" on the Mac basically work by ID (with a fallback to + the absolute path in more recent AFP clients. But this applies only to + Finder, not to applications). + + Every file in an AFP volume has to have a unique file ID + FID + + File ID + , IDs must, according to the specs, never be reused, and + IDs are 32 bit numbers (Directory IDs use the same ID pool). So, after + ~4 billion files/folders have been written to an AFP volume, the ID pool + is depleted and no new file can be written to the volume. No whining + please :-) + + Netatalk needs to map IDs to files and folders in the host + filesystem. To achieve this, several different CNID backends + CNID backend + are available and can be choosed by the + cnidscheme + + specifying a CNID backend + option in the + afp.conf + + 5 + configuration file. A CNID backend is basically a + database storing ID <-> name mappings. + + The CNID Databases are by default located in + /var/netatalk/CNID. + + There is a command line utility called dbd + available which can be used to verify, repair and rebuild the CNID + database. + + + There are some CNID related things you should keep in mind when + working with netatalk: + + + + Don't nest volumes + Nested volumes + . + + + + CNID backends are databases, so they turn afpd into a file + server/database mix. + + + + If there's no more space on the filesystem left, the + database will get corrupted. You can work around this by either + using the option and put the database + files into another location or, if you use quotas, make sure the + CNID database folder is owned by a user/group without a + quota + Quotas + + Disk usage quotas + . + + + + Be careful with CNID databases for volumes that are mounted + via NFS. That is a pretty audacious decision to make anyway, but + putting a database there as well is really asking for trouble, + i.e. database corruption. Use the + directive to put the databases onto a local disk if you must use + NFS + NFS + + Network File System + mounted volumes. + + + + + + cdb<indexterm> + <primary>CDB</primary> + + <secondary>"cdb" CNID backend</secondary> + </indexterm> + + The "concurrent database" backend is based on Berkeley DB. With + this backend, several afpd daemons access the CNID database directly. + Berkeley DB locking is used to synchronize access, if more than one + afpd process is active for a volume. The drawback is, that the crash + of a single afpd process might corrupt the database. cdb should only + be used when sharing home directories for a larger number of users + and it has been determined that a large number of + cnid_dbd processes is problematic. + + + + dbd<indexterm> + <primary>DBD</primary> + + <secondary>"dbd" CNID backend</secondary> + </indexterm> + + Access to the CNID database is restricted to the cnid_dbd daemon + process. afpd processes communicate with the daemon for database reads + and updates. The probability for database corruption is practically + zero. + + This is the default backend since Netatalk 2.1. + + + + tdb<indexterm> + <primary>tdb</primary> + + <secondary>"tdb" CNID backend</secondary> + </indexterm> + + tdb is another persistent CNID database, it's + Samba's Trivial Database. It could be used + instead of cdb for user volumes. + Only ever use it for volumes that are + not shared and accessed by multiple clients + at once ! + This backend is also used internally (as in-memory CNID + database) as a fallback in case opening the primary database can't be + opened, because tdb can work as in-memory database. + This of course means upon restart the CNIDs are gone. + + + + last<indexterm> + <primary>Last</primary> + + <secondary>"last" CNID backend</secondary> + </indexterm> + + The last backend is a in-memory tdb database. It is not + persistent. Starting with netatalk 3.0, it becomes the read + only mode automatically. This is useful e.g. for + CD-ROMs. + + + + + Charsets<indexterm> + <primary>Charset</primary> + + <secondary>character set</secondary> + </indexterm>/Unicode<indexterm> + <primary>Unicode</primary> + </indexterm> + + + + + Why Unicode? + + Internally, computers don't know anything about characters and + texts, they only know numbers. Therefore, each letter is assigned a + number. A character set, often referred to as + charset or + codepage + Codepage + , defines the mappings between numbers and + letters. + + If two or more computer systems need to communicate with each + other, the have to use the same character set. In the 1960s the + ASCII + ASCII + + American Standard Code for Information + Interchange + (American Standard Code for Information Interchange) + character set was defined by the American Standards Association. The + original form of ASCII represented 128 characters, more than enough to + cover the English alphabet and numerals. Up to date, ASCII has been + the normative character scheme used by computers. + + Later versions defined 256 characters to produce a more + international fluency and to include some slightly esoteric graphical + characters. Using this mode of encoding each character takes exactly + one byte. Obviously, 256 characters still wasn't enough to map all the + characters used in the various languages into one character + set. + + As a result localized character sets were defined later, e.g the + ISO-8859 character sets. Most operating system vendors introduced + their own characters sets to satisfy their needs, e.g. IBM defined the + codepage 437 (DOSLatinUS), Apple introduced the + MacRoman + MacRoman + + MacRoman charset + codepage and so on. The characters that were assigned + number larger than 127 were referred to as + extended characters. These character sets + conflict with another, as they use the same number for different + characters, or vice versa. + + Almost all of those characters sets defined 256 characters, + where the first 128 (0-127) character mappings are identical to ASCII. + As a result, communication between systems using different codepages + was effectively limited to the ASCII charset. + + To solve this problem new, larger character sets were defined. + To make room for more character mappings, these character sets use at + least 2 bytes to store a character. They are therefore referred to as + multibyte character sets. + + One standardized multibyte charset encoding scheme is known as + unicode. A big advantage + of using a multibyte charset is that you only need one. There is no + need to make sure two computers use the same charset when they are + communicating. + + + + character sets used by Apple + + In the past, Apple clients used single-byte charsets to + communicate over the network. Over the years Apple defined a number of + codepages, western users will most likely be using the + MacRoman codepage. + + Codepages defined by Apple include: + + + + MacArabic, MacFarsi + + + + MacCentralEurope + + + + MacChineseSimple + + + + MacChineseTraditional + + + + MacCroation + + + + MacCyrillic + + + + MacDevanagari + + + + MacGreek + + + + MacHebrew + + + + MacIcelandic + + + + MacJapanese + + + + MacKorean + + + + MacRoman + + + + MacRomanian + + + + MacThai + + + + MacTurkish + + + + Starting with Mac OS X and AFP3, UTF-8 is used. UTF-8 encodes + Unicode characters in an ASCII compatible way, each Unicode character + is encoded into 1-6 ASCII characters. UTF-8 is therefore not really a + charset itself, it's an encoding of the Unicode charset. + + To complicate things, Unicode defines several normalization + forms. While samba + Samba + uses precomposed + Precomposed + + Precomposed Unicode normalization + Unicode, which most Unix tools prefer as well, Apple + decided to use the decomposed + Decomposed + + Decomposed Unicode normalization + normalization. + + For example lets take the German character + 'ä'. Using the precomposed normalization, Unicode + maps this character to 0xE4. In decomposed normalization, 'ä' is + actually mapped to two characters, 0x61 and 0x308. 0x61 is the mapping + for an 'a', 0x308 is the mapping for a COMBINING + DIAERESIS. + + Netatalk refers to precomposed UTF-8 as + UTF8 + UTF8 + + Netatalk's precomposed UTF-8 encoding + and to decomposed UTF-8 as + UTF8-MAC + UTF8-MAC + + Netatalk's decomposed UTF-8 encoding + . + + + + afpd and character sets + + To support new AFP 3.x and older AFP 2.x clients at the same + time, afpd needs to be able to convert between the various charsets + used. AFP 3.x clients always use UTF8-MAC, AFP 2.x clients use one of + the Apple codepages. + + At the time of this writing, netatalk supports the following + Apple codepages: + + + + MAC_CENTRALEUROPE + + + + MAC_CHINESE_SIMP + + + + MAC_CHINESE_TRAD + + + + MAC_CYRILLIC + + + + MAC_GREEK + + + + MAC_HEBREW + + + + MAC_JAPANESE + + + + MAC_KOREAN + + + + MAC_ROMAN + + + + MAC_TURKISH + + + + afpd handles three different character set options: + + + + unix charset + unix charset + + afpd's unix charset setting + + + + This is the codepage used internally by your operating + system. If not specified, it defaults to . + If is specified and your system support + Unix locales, afpd tries to detect the codepage. afpd uses this + codepage to read its configuration files, so you can use + extended characters for volume names, login messages, etc. see + + afp.conf + + 5 + . + + + + + mac charset + mac charset + + afpd's mac charset setting + + + + As already mentioned, older Mac OS clients (up to AFP 2.2) + use codepages to communicate with afpd. However, there is no + support for negotiating the codepage used by the client in the + AFP protocol. If not specified otherwise, afpd assumes the + MacRoman codepage is used. In case you're + clients use another codepage, e.g. + MacCyrillic, you'll have to explicitly configure this. see + + afp.conf + + 5 + . + + + + + vol charset + vol charset + + afpd's vol charset setting + + + + This defines the charset afpd should use for filenames on + disk. By default, it is the same as . If you have iconv + Iconv + + iconv encoding conversion engine + installed, you can use any iconv provided charset + as well. + + afpd needs a way to preserve extended macintosh + characters, or characters illegal in unix filenames, when saving + files on a unix filesystem. Earlier versions used the the so + called CAP encoding + CAP encoding + + CAP style character encoding + . An extended character (>0x7F) would be + converted to a :xx hex sequence, e.g. the Apple Logo (MacRoman: + 0xF0) was saved as :f0. Some special characters will be + converted as to :xx notation as well. '/' will be encoded to + :2f, if was not specified, a leading + dot '.' will be encoded as :2e. + + Even though this version now uses as + the default encoding for filenames, '/' will be converted to + ':'. For western users another useful setting could be + . + + If a character cannot be converted from the to the selected , + afpd will save it as a CAP encoded character. For AFP3 clients, + afpd will convert the UTF8 character to first. If this conversion fails, you'll receive + a -50 error on the mac. Note: Whenever you + can, please stick with the default UTF8 volume format. see + + afp.conf + + 5 + . + + + + + + + + Authentication<indexterm> + <primary>Authentication</primary> + + <secondary>between AFP client and server</secondary> + </indexterm> + + + AFP authentication basics + + Apple chose a flexible model called "User Authentication + Modules" + UAM + + User Authentication Module + (UAMs) for authentication purposes between AFP client + and server. An AFP client initially connecting to an AFP server will + ask for the list of UAMs which the server provides, and will choose + the one with strongest encryption that the client supports. + + Several UAMs have been developed by Apple over the time, some by + 3rd-party developers. + + + + UAMs supported by Netatalk + + Netatalk supports the following ones by default: + + + + "No User Authent" + No User Authent + + "No User Authent" UAM (guest access) + UAM (guest access without authentication) + + + + "Cleartxt Passwrd" + Cleartxt Passwrd + + "Cleartxt Passwrd" UAM + UAM (no password encryption) + + + + "Randnum exchange" + Randnum exchange + + "Randnum exchange" UAM + /"2-Way Randnum exchange" + 2-Way Randnum exchange + + "2-Way Randnum exchange" UAM + UAMs (weak password encryption, separate password + storage) + + + + "DHCAST128" + DHCAST128 + + "DHCAST128" UAM + UAM (stronger password encryption) + + + + "DHX2" + DHX2 + + "DHX2" UAM + UAM (successor of DHCAST128) + + + + There exist other optional UAMs as well: + + + + "PGPuam 1.0" + PGPuam 1.0 + + "PGPuam 1.0" UAM + + uams_pgp.so + + "PGPuam 1.0" UAM + UAM (PGP-based authentication for pre-Mac OS X + clients. You'll also need the PGPuam + client to let this work) + + You'll have to add "--enable-pgp-uam" + to your configure switches to have this UAM available. + + + + "Kerberos IV" + Kerberos IV + + "Kerberos IV" UAM + + uams_krb4.so + + "Kerberos IV" UAM + /"AFS Kerberos" + AFS Kerberos + + "AFS Kerberos" UAM (Kerberos IV) + UAMs (suitable to use Kerberos + v4 based authentication and AFS file servers) + + Use "--enable-krb4-uam" at compile time + to activate the build of this UAM. + + + + "Client Krb v2" + Client Krb v2 + + "Client Krb v2" UAM (Kerberos V) + UAM (Kerberos V, suitable for "Single Sign On" + Scenarios with OS X clients -- see below) + + "--enable-krbV-uam" will provide you + with the ability to use this UAM. + + + + You can configure which UAMs should be activated by defining + "" in section. + afpd will log which UAMs it's using and if problems + occur while activating them in either + netatalk.log or syslog at startup time. + + asip-status.pl + + 1 + can be used to query the available UAMs of AFP + servers as well. + + Having a specific UAM available at the server does not + automatically mean that a client can use it. Client-side support is + also necessary. For older Macintoshes running Mac OS < X DHCAST128 + support exists since AppleShare client 3.8.x. + + On OS X, there exist some client-side techniques to make the + AFP-client more verbose, so one can have a look what's happening while + negotiating the UAMs to use. Compare with this hint. + + + + Which UAMs to activate? + + It depends primarily on your needs and on the kind of Mac OS + versions you have to support. Basically one should try to use + DHCAST128 and DHX2 where possible because of its strength of password + encryption. + + + + Unless you really have to supply guest access to your + server's volumes ensure that you disable "No User Authent" since + it might lead accidentally to unauthorized access. In case you + must enable guest access take care that you enforce this on a per + volume base using the access controls. + + + + The "ClearTxt Passwrd" UAM is as bad as it sounds since + passwords go unencrypted over the wire. Try to avoid it at both + the server's side as well as on the client's. Note: If you want to + provide Mac OS 8/9 clients with NetBoot-services then you need + uams_cleartext.so since the AFP-client integrated into the Mac's + firmware can only deal with this basic form of + authentication. + + + + Since "Randnum exchange"/"2-Way Randnum exchange" uses only + 56 bit DES for encryption it should be avoided as well. Another + disadvantage is the fact that the passwords have to be stored in + cleartext on the server and that it doesn't integrate into both + PAM scenarios or classic /etc/shadow (you have to administrate + passwords separately by using the + afppasswd + + 1 + utility, if clients should use these + UAMs) + + + + "DHCAST128" or "DHX2" should be a good compromise for most + people since it combines stronger encryption with PAM + integration. + + + + Using the Kerberos V + Kerberos V + + "Client Krb v2" UAM + ("Client Krb v2") UAM, it's possible to implement + real single sign on scenarios using Kerberos tickets. The password + is not sent over the network. Instead, the user password is used + to decrypt a service ticket for the appleshare server. The service + ticket contains an encryption key for the client and some + encrypted data (which only the appleshare server can decrypt). The + encrypted portion of the service ticket is sent to the server and + used to authenticate the user. Because of the way that the afpd + service principal detection is implemented, this authentication + method is vulnerable to man-in-the-middle attacks. + + + + For a more detailed overview over the technical implications of + the different UAMs, please have a look at Apple's File + Server Security pages. + + + + Using different authentication sources with specific + UAMs + + Some UAMs provide the ability to use different authentication + "backends", namely uams_cleartext.so, + uams_dhx.so and + uams_dhx2.so. They can use either classic Unix + passwords from /etc/passwd + (/etc/shadow) or PAM if the system supports that. + uams_cleartext.so can be symlinked to either + uams_passwd.so or + uams_pam.so, uams_dhx.so to + uams_dhx_passwd.so or + uams_dhx_pam.so and + uams_dhx2.so to + uams_dhx2_passwd.so or + uams_dhx2_pam.so. + + So, if it looks like this in Netatalk's UAMs folder (per default + /etc/netatalk/uams/):uams_clrtxt.so -> uams_pam.so +uams_dhx.so -> uams_dhx_pam.so +uams_dhx2.so -> uams_dhx2_pam.so then you're using PAM, + otherwise classic Unix passwords. The main advantage of using PAM is + that one can integrate Netatalk in centralized authentication + scenarios, eg. via LDAP, NIS and the like. Please always keep in mind + that the protection of your user's login credentials in such scenarios + also depends on the strength of encryption that the UAM in question + supplies. So think about eliminating weak UAMs like "ClearTxt Passwrd" + and "Randnum exchange" completely from your network. + + + + Netatalk UAM overview table + + A small overview of the most common used UAMs. + + + Netatalk UAM overview + + + + + + + + + + + + + + + + + + + UAM + + No User Authent + uams_guest.so + + "No User Authent" UAM (guest + access) + + + Cleartxt Passwrd + uams_cleartxt.so + + "Cleartxt Passwrd" UAM + + + (2-Way) Randnum exchange + uams_randnum.so + + "(2-Way) Randnum exchange" UAM + + + DHCAST128 + uams_dhx.so + + "DHCAST128" UAM + + + DHX2 + uams_dhx2.so + + "DHX2" UAM + + + Client Krb v2 + uams_gss.so + + "Client Krb v2" UAM (Kerberos V) + + + + + pssword + length + + guest access + + max. 8 characters + + max. 8 characters + + max. 64 characters + + max. 256 characters + + Kerberos tickets + + + + Client + support + + built-in into all Mac OS versions + + built-in in all Mac OS versions except 10.0. Has to be + activated explicitly in recent Mac OS X versions + + built-in into almost all Mac OS versions + + built-in since AppleShare client 3.8.4, available as a + plug-in for 3.8.3, integrated in Mac OS X' AFP client + + built-in since Mac OS X 10.2 + + built-in since Mac OS X 10.2 + + + + Encryption + + Enables guest access without authentication between + client and server. + + Password will be sent in cleartext over the wire. Just + as bad as it sounds, therefore avoid at all if possible (note: + providing NetBoot services requires the ClearTxt UAM) + + 8-byte random numbers are sent over the wire, + comparable with DES, 56 bits. Vulnerable to offline dictionary + attack. Requires passwords in clear on the server. + + Password will be encrypted with 128 bit SSL, user will + be authenticated against the server but not vice versa. + Therefor weak against man-in-the-middle attacks. + + Password will be encrypted using libgcrypt with CAST + 128 in CBC mode. User will be authenticated against the server + but not vice versa. Therefor weak against man-in-the-middle + attacks. + + Password is not sent over the network. Due to the + service principal detection method, this authentication method + is vulnerable to man-in-the-middle attacks. + + + + Server + support + + uams_guest.so + + uams_cleartxt.so + + uams_randnum.so + + uams_dhx.so + + uams_dhx2.so + + uams_gss.so + + + + Password + storage method + + None + + Either /etc/passwd + (/etc/shadow) or PAM + + Passwords stored in + clear text in a separate text file + + Either /etc/passwd + (/etc/shadow) or PAM + + Either /etc/passwd + (/etc/shadow) or PAM + + At the Kerberos Key + Distribution Center* + + + +
+ + * Have a look at this Kerberos + overview +
+ + + SSH tunneling + + Tunneling and all sort of VPN stuff has nothing to do with AFP + authentication and UAMs in general. But since Apple introduced an + option called "Allow Secure Connections Using SSH" and many people + tend to confuse both things, we'll speak about that here too. + + + Manually tunneling an AFP session + + This works since the first AFP servers that spoke "AFP over + TCP" appeared in networks. One simply tunnels the remote server's + AFP port to a local port different than 548 and connects locally to + this port afterwards. On OS X this can be done by + + ssh -l $USER $SERVER -L 10548:127.0.0.1:548 sleep 3000 + + After establishing the tunnel one will use + "afp://127.0.0.1:10548" in the "Connect to + server" dialog. All AFP traffic including the initial connection + attempts will be sent encrypted over the wire since the local AFP + client will connect to the Mac's local port 10548 which will be + forwarded to the remote server's AFP port (we used the default 548) + over SSH. + + These sorts of tunnels are an ideal solution if you've to + access an AFP server providing weak authentications mechanisms + through the Internet without having the ability to use a "real" VPN. + Note that you can let ssh compress the data by + using its "-C" switch and that the tunnel endpoints can be different + from both AFP client and server (compare with the SSH documentation + for details). + + + + Automatically establishing a tunneled AFP connection + + From Mac OS X 10.2 to 10.4, Apple added an "Allow Secure + Connections Using SSH" checkbox to the "Connect to Server" dialog. + The idea behind: When the server signals that it can be contacted by + SSH then Mac OS X' AFP client tries to establish the tunnel and + automagically sends all AFP traffic through it. + + But it took until the release of Mac OS X 10.3 that this + feature worked the first time... partly. In case, the SSH tunnel + can't be established the AFP client silently fell back to an unencrypted AFP + connection attempt. + + Netatalk's afpd will report that it is capable of handling SSH + tunneled AFP requests, when both "" + and "" options are set in + section (double check with + asip-status.pl + + 1 + after you restarted afpd when you made changes to + the settings). But there are a couple of reasons why you don't want + to use this option at all: + + + + Tunneling TCP over TCP (as SSH does) is not the best idea. + There exist better solutions like VPNs based on the IP + layer. + + + + Since this SSH kludge isn't a normal UAM that integrates + directly into the AFP authentication mechanisms but instead uses + a single flag signalling clients whether they can try to establish a tunnel or not, it + makes life harder to see what's happening when things go + wrong. + + + + You cannot control which machines are logged on by + Netatalk tools like a macusers since all + connection attempts seem to be made from localhost. + + + + On the other side you've to limit access to afpd to + localhost only (TCP wrappers) when you want to ensure that all + AFP sessions are SSH encrypted or... + + + + ...when you're using 10.2 - 10.3.3 then you get the + opposite of what you'd expect: potentially unencrypted AFP + communication (including logon credentials) on the network + without a single notification that establishing the tunnel + failed. Apple fixed that not until Mac OS X 10.3.4. + + + + Encrypting all AFP sessions via SSH can lead to a + significantly higher load on the Netatalk server + + + + +
+ + + ACL Support<indexterm> + <primary>ACLs</primary> + </indexterm> + + ACL support for AFP is implemented for ZFS ACLs on Solaris and + derived platforms and for POSIX 1e ACLs on Linux. + + + Configuration + + For a basic mode of operation there's nothing to configure. + Netatalk reads ACLs on the fly and calculates effective permissions + which are then send to the AFP client via the so called + UARights + UARights + permission bits. On a Mac, the Finder uses these bits + to adjust permission in Finder windows. For example folder whos UNIX + mode would only result in in read-only permissions for a user will not + be displayed with a read-only icon and the user will be able to write + to the folder given the folder has an ACL giving the user write + access. + + By default, the effective permission of the authenticated user + are only mapped to the mentioned UARights + UARights + permission structure, not the UNIX mode. You can adjust + this behaviour with the configuration option map acls. + + However, neither in Finder "Get Info" windows nor in Terminal + will you be able to see the ACLs, that's a result of how ACLs in OS X + are designed. If you want to be able to display ACLs on the client, + things get more involved as you must then setup both client and server + to be part on a authentication domain (directory service, eg LDAP, + OpenDirectory). The reason is, that in OS X ACLs are bound to UUIDs, + not just uid's or gid's. Therefor afpd must be able to map every + filesystem uid and gid to a UUID so that it can return the server side + ACLs which are bound to UNIX uid and gid mapped to OS X UUIDs. + + Netatalk can query a directory server using LDAP queries. Either + the directory server already provides an UUID attribute for user and + groups (Active Directory, Open Directory) or you reuse an unused + attribute (or add a new one) to you directory server (eg + OpenLDAP). + + In detail: + + + + For Solaris/ZFS: ZFS Volumes + + You should configure a ZFS ACL know for any volume you want + to use with Netatalk: + + aclinherit = passthrough +aclmode = passthrough + + For an explanation of what this knob does and how to apply + it, check your hosts ZFS documentation (eg man zfs). + + + + Authentication Domain + + Your server and the clients must be part of a security + association where identity data is coming from a common source. + ACLs in Darwin are based on UUIDs and so is the ACL specification + in AFP 3.2. Therefor your source of identity data has to provide + an attribute for every user and group where a UUID is stored as a + ASCII string. In other words: + + + + you need an Open Directory Server or an LDAP server + where you store UUIDs in some attribute + + + + your clients must be configured to use this + server + + + + your server should be configured to use this server via + nsswitch and PAM + + + + configure Netatalk via the special LDAP options for ACLs in afp.conf so that Netatalk is able + to retrieve the UUID for users and groups via LDAP search + queries + + + + + + + + OS X ACLs + + With Access Control Lists (ACLs) Mac OS X offers a powerful + extension of the traditional UNIX permissions model. An ACL is an + ordered list of Access Control Entries (ACEs) explicitly granting or + denying a set of permissions to a given user or group. + + Unlike UNIX permissions, which are bound to user or group IDs, + ACLs are tied to UUIDs. For this reason accessing an object's ACL + requires server and client to use a common directory service which + translates between UUIDs and user/group IDs. + + ACLs and UNIX permissions interact in a rather simple way. As + ACLs are optional UNIX permissions act as a default mechanism for + access control. Changing an objects's UNIX permissions will leave it's + ACL intact and modifying an ACL will never change the object's UNIX + permissions. While doing access checks, OS X first examines an + object's ACL evaluating ACEs in order until all requested rights have + been granted, a requested right has been explicitly denied by an ACE + or the end of the list has been reached. In case there is no ACL or + the permissions granted by the ACL are not sufficient to fulfill the + request, OS X next evaluates the object's UNIX permissions. Therefore + ACLs always have precedence over UNIX permissions. + + + + ZFS ACLs + + ZFS ACLs closely match OS X ACLs. Both offer mostly identical + fine grained permissions and inheritance settings. + + + + POSIX ACLs + + + Overview + + Compared to OS X or NFSv4 ACLs, Posix ACLs represent a + different, less versatile approach to overcome the limitations of + the traditional UNIX permissions. Implementations are based on the + withdrawn Posix 1003.1e standard. + + The standard defines two types of ACLs. Files and directories + can have access ACLs which are consulted for access checks. + Directories can also have default ACLs irrelevant to access checks. + When a new object is created inside a directory with a default ACL, + the default ACL is applied to the new object as it's access ACL. + Subdirectories inherit default ACLs from their parent. There are no + further mechanisms of inheritance control. + + Architectural differences between Posix ACLs and OS X ACLs + especially involve: + + + + No fine-granular permissions model. Like UNIX + permissions Posix ACLs only differentiate between read, write + and execute permissions. + + + + Entries within an ACL are unordered. + + + + Posix ACLs can only grant rights. There is no way to + explicitly deny rights by an entry. + + + + UNIX permissions are integrated into an ACL as special + entries. + + + + Posix 1003.1e defines 6 different types of ACL entries. The + first three types are used to integrate standard UNIX permissions. + They form a minimal ACL, their presence is mandatory and only one + entry of each type is allowed within an ACL. + + + + ACL_USER_OBJ: the owner's access rights. + + + + ACL_GROUP_OBJ: the owning group's access rights. + + + + ACL_OTHER: everybody's access rights. + + + + The remaining entry types expand the traditional permissions + model: + + + + ACL_USER: grants access rights to a certain user. + + + + ACL_GROUP: grants access rights to a certain + group. + + + + ACL_MASK: limits the maximum access rights which can be + granted by entries of type ACL_GROUP_OBJ, ACL_USER and + ACL_GROUP. As the name suggests, this entry acts as a mask. + Only one ACL_MASK entry is allowed per ACL. If an ACL contains + ACL_USER or ACL_GROUP entries, an ACL_MASK entry must be + present too, otherwise it is optional. + + + + In order to maintain compatibility with applications not aware + of ACLs, Posix 1003.1e changes the semantics of system calls and + utilities which retrieve or manipulate an objects UNIX permissions. + In case an object only has a minimal ACL, the group permissions bits + of the UNIX permissions correspond to the value of the ACL_GROUP_OBJ + entry. + + However, if the ACL also contains an ACL_MASK entry, the + behavior of those system calls and utilities is different. The group + permissions bits of the UNIX permissions correspond to the value of + the ACL_MASK entry, i. e. calling "chmod g-w" will not only revoke + write access for the group, but for all entities which have been + granted write access by ACL_USER or ACL_GROUP entries. + + + + Mapping POSIX ACLs to OS X ACLs + + When a client wants to read an object's ACL, afpd maps it's + Posix ACL onto an equivalent OS X ACL. Writing an object's ACL + requires afpd to map an OS X ACL onto a Posix ACL. Due to + architectural restrictions of Posix ACLs, it is usually impossible + to find an exact mapping so that the result of the mapping process + will be an approximation of the original ACL's semantic. + + + + afpd silently discard entries which deny a set of + permissions because they they can't be represented within the + Posix architecture. + + + + As entries within Posix ACLs are unordered, it is + impossible to preserve order. + + + + Inheritance control is subject to severe limitations as + well: + + Entries with the only_inherit flag set will only + become part of the directory's default ACL. + + + + Entries with at least one of the flags + file_inherit, directory_inherit or limit_inherit set, + will become part of the directory's access and default + ACL, but the restrictions they impose on inheritance + will be ignored. + + + + + + The lack of a fine-granular permission model on the + Posix side will normally result in an increase of granted + permissions. + + + + As OS X clients aren't aware of the Posix 1003.1e specific + relationship between UNIX permissions and ACL_MASK, afpd does not + expose this feature to the client to avoid compatibility issues and + handles *unix permissions and ACLs the same way as Apple's reference + implementation of AFP does. When an object's UNIX permissions are + requested, afpd calculates proper group rights and returns the + result together with the owner's and everybody's access rights to + the caller via "permissions" and "ua_permissions" members of the + FPUnixPrivs structure (see Apple Filing Protocol Reference, page + 181). Changing an object's permissions, afpd always updates + ACL_USER_OBJ, ACL_GROUP_OBJ and ACL_OTHERS. If an ACL_MASK entry is + present too, afpd recalculates it's value so that the new group + rights become effective and existing entries of type ACL_USER or + ACL_GROUP stay intact. + + + + + + Filesystem Change Events<indexterm> + <primary>FCE</primary> + </indexterm> + + Netatalk includes a nifty filesystem change event mechanism where + afpd processes notfiy interested listeners about certain filesystem + event by UDP network datagrams. + + For the format of the UDP packets and for an example C application + that demonstrates how to use these in a listener, take a look at the + Netatalk sourcefile bin/misc/fce.c. + + The currently supported FCE events are + + file modification (fmod) + + + + file deletion (fdel) + + + + directory deletion (ddel) + + + + file creation (fcre) + + + + directory deletion (ddel) + + + + For details on the available simple configuration options take a + look at afp.conf. + +
+ + + Starting and stopping Netatalk + + The Netatalk distribution comes with several operating system + specific startup script templates that are tailored according to the + options given to the "configure" script before compiling. Currently, + templates are provided for RedHat (sysv style), RedHat (systemd style), + SUSE (sysv style), SUSE (systemd style), Gentoo, NetBSD, Debian and + Solaris. You can select to install the generated startup script(s) + + Startscript + + startup script + by specifying a system type to "configure". To + automatically install startup scripts give one of the available + option to "configure". + + Since new releases of Linux distributions appear all the time and + the startup procedure for the other systems mentioned above might change + as well, it is probably a good idea to not blindly install a startup + script but to look at it first to see if it will work on your system. If + you use Netatalk as part of a fixed setup, like a Linux distribution, an + RPM or a BSD package, things will probably have been arranged properly for + you. The following therefore applies mostly for people who have compiled + Netatalk themselves. + + The following daemon need to be started by whatever startup script + mechanism is used: + + + + netatalk + netatalk + + + + + Additionally, make sure that the configuration file + afp.conf is in the right place. + +
diff --git a/doc/manual/install.xml b/doc/manual/install.xml new file mode 100644 index 00000000..a64b3846 --- /dev/null +++ b/doc/manual/install.xml @@ -0,0 +1,347 @@ + + + + 24.8.2012 + + + Installation + + + If you have previously used an older version of Netatalk, please + read the chapter about upgrading first + !!! + + + + + + How to obtain Netatalk + + Please have a look at the netatalk page on sourceforge for the most + recent informations on this issue. + + http://sourceforge.net/projects/netatalk/ + + + Binary packages + + Binary packages of Netatalk are included in some Linux and UNIX + distributions. You might want to have a look at the usual locations, + too. + + Ubuntu package: https://launchpad.net/ubuntu + + + Debian package: http://packages.debian.org/ + + + various RPM package: http://rpmfind.net/ + + Fedora/RHEL package: http://koji.fedoraproject.org/koji/search + + + Gentoo package: http://packages.gentoo.org/ + + + openSUSE package: http://software.opensuse.org/ + + + Solaris package: http://www.blastwave.org/ + + + FreeBSD ports: http://www.freebsd.org/ports/index.html + + + NetBSD pkgsrc: http://pkgsrc.se/search.php + + + OpenBSD ports:http://openports.se/search.php + + + etc. + RPM + + Red Hat Package Manager package + + Deb + + Debian package + + Ports + + FreeBSD port + + + + + Source packages + + + Tarballs + + Prepacked tarballs in .tar.gz and tar.bz2 format are available + on the netatalk page on sourceforge. + + + + Git + + Downloading the Git repository can be done quickly and + easily. + + + + Make sure you have Git installed. which + git should produce a path to git. + + $> which git +/usr/bin/git + + + + If you don't have one make a source directory. + cd to this directory. + + $> mkdir /path/to/new/source/dir +$> cd /path/to/new/source/dir + + + + Now get the source: + + $> git clone git://git.code.sf.net/p/netatalk/code netatalk-code +Initialized empty Git repository in /path/to/new/source/dir/netatalk/.git/ +remote: Counting objects: 2503, done. +... + + + This will create a local directory called "netatalk-code" + containing a complete and fresh copy of the whole Netatalk source + from the Git repository. + + + + In order to keep your repository copy updated, occasionally + run: + + $> git pull + + + + Now cd to the netatalk directory and run + ./bootstrap. This will create the + configure script required in the next + step. + + $> ./bootstrap + + + + + + + + + + Compiling Netatalk + + + Prerequisites + + + Required third party software + + + + Berkeley DB + BDB + + Berkeley DB + . + + At the time of writing, the following versions are + supported: + + + + minimum 4.6.x + + + + In case Berkeley DB is not installed on your system, please + download it from: + + + http://www.oracle.com/technetwork/products/berkeleydb/downloads/index.html + + and follow the installation + instructions. + + + + Libgcrypt + + Required for OS X 10.7 and later. Libgcrypt is needed for + DHX2. + + Libgcrypt can be downloaded from: + http://directory.fsf.org/wiki/Libgcrypt. + + + + + + Optional third party software + + Netatalk can use the following third party software to enhance + it's functionality. + + + + mDNSresponderPOSIX or Avahi for Bonjour (aka + Zeroconf) + + Mac OS X 10.2 and later use Bonjour (aka Zeroconf) for + service discovery. + + Avahi must be build with DBUS support ( + --enable-dbus). + + You can download Avahi from: http://www.avahi.org/. + + You can download mDNSresponder from: http://opensource.apple.com/tarballs/mDNSResponder/. + + + + TCP wrappers + + Wietse Venema's network logger, also known as TCPD or + LOG_TCP. + + Security options are: access control per host, domain and/or + service; detection of host name spoofing or host address spoofing; + booby traps to implement an early-warning system. + + TCP Wrappers can be downloaded from: ftp://ftp.porcupine.org/pub/security/ + + + + PAM + PAM + + Pluggable Authentication Modules + + + PAM provides a flexible mechanism for authenticating users. + PAM was invented by SUN + SUN + + Sun Microsystems + Microsystems. Linux-PAM is a suite of shared + libraries that enable the local system administrator to choose how + applications authenticate users. + + You can get the Linux PAM documentation and sources from + http://www.kernel.org/pub/linux/libs/pam/. + + + + iconv + + iconv provides conversion routines for many character + encodings. Netatalk uses it to provide charsets it does not have + built in conversions for, like ISO-8859-1. On glibc systems, + Netatalk can use the glibc provided iconv implementation. + Otherwise you can use the GNU libiconv implementation. + + You can download GNU libiconv from: http://www.gnu.org/software/libiconv/. + + + + + + + Compiling<indexterm> + <primary>Compile</primary> + + <secondary>Compiling Netatalk from Source</secondary> + </indexterm> Netatalk + + + Configuring the build + + To build the binaries, first run the program + ./configure in the source directory. This should + automatically configure Netatalk for your operating system. If you + have unusual needs, then you may wish to run + + $> ./configure --help + + to see what special options you can enable. + + The most used configure options are: + + + + =redhat-sysv|redhat-systemd|suse-sysv|suse-systemd|gentoo|netbsd|debian|solaris|systemd + + This option helps netatalk to determine where to install the + start scripts. + + + + =/path/to/bdb/installation/ + + In case you installed Berkeley DB in a non-standard + location, you will have to give the install + location to netatalk, using this switch. + + + + Now run configure with any options you need + + $> ./configure [arguments] + + Configure will end up in an overview showing the settings the + Netatalk Makefiles have been created with. + + If this step fails please visit the troubleshooting + guide. + + Next, running + + $> make + + should produce the Netatalk binaries (this step can take several + minutes to complete). + + When the process finished you can use + + $> make install + + to install the binaries and documentation (must be done as + "root" when using default locations). + + + + diff --git a/doc/manual/intro.xml b/doc/manual/intro.xml new file mode 100644 index 00000000..0b70a9af --- /dev/null +++ b/doc/manual/intro.xml @@ -0,0 +1,15 @@ + + + Introduction to Netatalk + + Netatalk is an OpenSource software package, that can be used to turn + a *NIX machine into an extremely high-performance and + reliable file server for Macintosh computers. + + Using Netatalk's AFP 3.3 compliant file-server leads to significantly + higher transmission speeds compared with Macs accessing a server via + SaMBa/NFS while providing clients with the best possible user experience + (full support for Macintosh metadata, flawlessly supporting mixed + environments of classic Mac OS and OS X clients) + + diff --git a/doc/manual/manual.xml.in b/doc/manual/manual.xml.in new file mode 100644 index 00000000..eab6e628 --- /dev/null +++ b/doc/manual/manual.xml.in @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + Netatalk 3.0 Manual + + + 03-24-2013 + @NETATALK_VERSION@ + + + + + Legal Notice + +This documentation is distributed under the GNU General Public License (GPL) version 2. +A copy of the license is included in this documentation, as well as within the Netatalk source +distribution. An on-line copy can be found at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt + + + + + + + + + + &Intro; + + + &Install; + + + &Configuration; + + + &Upgrade; + + + + Manual Pages + + This is a collection of the man pages delivered with Netatalk. + + &ad.1; + + &afp.conf.5; + + &afp_signature.conf.5; + + &afp_voluuid.conf.5; + + &afpd.8; + + &afpldaptest.1; + + &afppasswd.1; + + &afpstats.1; + + &apple_dump.1; + + &asip-status.pl.1; + + &cnid_dbd.8; + + &cnid_metad.8; + + &dbd.1; + + &extmap.conf.5; + + &macusers.1; + + &megatron.1; + + &netatalk.8; + + &netatalk-config.1; + + &uniconv.1; + + + + + + + Index + diff --git a/doc/manual/upgrade.xml b/doc/manual/upgrade.xml new file mode 100644 index 00000000..ebd19633 --- /dev/null +++ b/doc/manual/upgrade.xml @@ -0,0 +1,1555 @@ + + + + 2.15.2013 + + + Frank + + Lahm + + + 15 Feb, 2013 + + + Upgrading from Netatalk 2 + + + Overview + + There are two major changes in Netatalk: + + New configuration file afp.conf, obsoleting all + previous configuration files + + + + New AppleDouble backend "" + which stores Mac metadata and resource forks in extended attributes + of the filesystem + + + + + New configuration + + + + ini style syntax (like Samba’s smb.conf) + + + + one to rule them all: configure AFP settings and volumes in + one file + + + + obsoletes afpd.conf, + netatalk.conf, + AppleVolumes.default and + afp_ldap.conf + + + most option names have changed, read the full manpage afp.conf for details + + + + + New AppleDouble backend + + New AppleDouble backend "" which + stores Mac metadata and resource forks in extended attributes of the + filesystem. + + default backend (!) + + + + requires a filesystem with Extended Attributes, fallback is + "" + + + + converts filesystems from "" + to "" on the fly when accessed + (can be disabled) + + + + dbd can be + used to do conversion in one shot + + + + Implementation details: + + stores Mac Metadata (eg FinderInfo, AFP Flags, Comment, + CNID) in an Extended Attributed named + “org.netatalk.Metadata” + + + + stores Mac ResourceFork either in + + an Extended Attribute named + “org.netatalk.ResourceFork” + on Solaris (FreeBSD?) w. ZFS, or in + + + + an extra AppleDouble file named “._file” for a file + named “file” + + + + + + the format of the ._ file is exactly as the Mac’s CIFS + client expects it when accessing the same filesystem via a CIFS + server (Samba), thus you can have parallel access from Macs to the + same dataset via AFP and CIFS without the risk of loosing data + (resources or metadata). Accessing the same dataset with CIFS + from Windows clients will still break the coupling of + “file” and “._file” + on non ZFS filesystems (see above), so for this we still + need an enhanced Samba VFS module (in the works). + + + + As these days the only applications making use of Resource Forks + are Adobe Photoshop (image preview) and Postscript Type 1 fonts, even on + eg Linux you’ll get rid of 99% of any extra Netatalk AppleDouble files + (and folders). + + + + Other major changes + + + + New service controller daemon netatalk which is responsible for + starting and restarting the AFP and CNID daemons. All bundled + start scripts have been updated, make sure to update yours! + + + + The CNID databases are now stored under + /var/netatalk/CNID/ + by default. You can use configure --localstatedir=PATH at + compile time to change the location. + + + + Netatalk 2.x volume options “usedots” and “upriv” now + enabled by default + + + + Removed SLP and AFP proxy support + + + + Removed type/creator extension mapping + support + + + + + + + Upgrading + + + + Stop Netatalk 2.x + + + + Install Netatalk 3 + + + + Manually recreate configuration in + and + + + + Update your Netatalk start script (SMF, systemd, whatever...) + to only start netatalk + + + + Move afp_voluuid.conf and + afp_signature.conf to the localstate directory (default + /var/netatalk/), you can use afpd -v + in order to find the correct path + + + + Start Netatalk 3 + + + + + + Notes + + + + Solaris ZFS permissions + + On Solaris with ZFS you have to make sure users have + filesystem permissions to read, create, modify (default: yes) and + delete (default: no) extended attributes. + + To grant this right to a group “staff” you’d use this + command: + + pfexec chmod A+group:staff:RW:fd:allow + /Volumes/test/ + + Remember to run this once before you share a volume so that + this permission inherits appropiately (fd flags in above + command). + + + + + Table with old and new configuration file names + + old and new configuration file names + + + + + + + Old File Name + New File Name + Description + + + + + - + etc/afp.conf + new ini-style format + + + - + etc/extmap.conf + starting with netatalk 3.0.2 + + + etc/netatalk/afp_signature.conf + var/netatalk/afp_signature.conf + moved to $localstatedir + + + etc/netatalk/afp_voluuid.conf + var/netatalk/afp_voluuid.conf + moved to $localstatedir + + + etc/netatalk/netatalk.conf + (/etc/default/netatalk) + - + obsolete + + + etc/netatalk/afpd.conf + - + obsolete + + + etc/netatalk/afp_ldap.conf + - + obsolete + + + etc/netatalk/AppleVolumes.default + - + obsolete + + + etc/netatalk/AppleVolumes.system + - + obsolete + + + ~/.AppleVolumes + - + obsolete + + + +
+
+ + + Table with old and new option names + + from netatalk.conf (/etc/default/netatalk) to afp.conf + + + + + + + + + + Old netatalk.conf + New afp.conf + Old Default Value + New Default Value + Section + Description + + + + + ATALK_NAME + hostname + - + - + (G) + use gethostname() by default + + + ATALK_UNIX_CHARSET + unix charset + LOCALE + UTF8 + (G) + - + + + ATALK_MAC_CHARSET + mac charset + MAC_ROMAN + MAC_ROMAN + (G)/(V) + - + + + CNID_METAD_RUN + - + yes + - + - + controlled by netatalk(8) + + + AFPD_RUN + - + yes + - + - + controlled by netatalk(8) + + + AFPD_MAX_CLIENTS + max connections + 20 + 200 + (G) + - + + + AFPD_UAMLIST + uam list + -U uams_dhx.so,uams_dhx2.so + uams_dhx.so uams_dhx2.so + (G) + - + + + AFPD_GUEST + guest account + nobody + nobody + (G) + - + + + CNID_CONFIG + log level + -l log_note + cnid:note + (G) + - + + + CNID_CONFIG + log file + - + - + (G) + - + + + ATALKD_RUN + - + no + - + - + AppleTalk is obsoleted + + + PAPD_RUN + - + no + - + - + AppleTalk is obsoleted + + + TIMELORD_RUN + - + no + - + - + AppleTalk is obsoleted + + + A2BOOT_RUN + - + no + - + - + AppleTalk is obsoleted + + + ATALK_BGROUND + - + no + - + - + AppleTalk is obsoleted + + + ATALK_ZONE + - + no + - + - + AppleTalk is obsoleted + + + +
+ from afpd.conf to afp.conf + + + + + + + + + + Old afpd.conf + New afp.conf + Old Default Value + New Default Value + Section + Description + + + + + 1st field ("-" or "server name") + hostname + - + - + (G) + use gethostname() by default + + + -uamlist + uam list + -U uams_dhx.so,uams_dhx2.so + uams_dhx.so uams_dhx2.so + (G) + - + + + -nozeroconf + zeroconf + - + yes (if supported) + (G) + - + + + -advertise_ssh + advertise ssh + - + no + (G) + - + + + -[no]savepassword + save password + -savepassword + yes + (G) + - + + + -[no]setpassword + set password + -nosetpassword + no + (G) + - + + + -client_polling + client polling + - + no + (G) + - + + + -hostname + hostname + - + - + (G) + use gethostname() by default + + + -loginmesg + login message + - + - + (G)/(V) + - + + + -guestname + guest account + nobody + nobody + (G) + - + + + -passwdfile + passwd file + afppasswd + afppasswd + (G) + - + + + -passwdminlen + passwd minlen + - + - + (G) + - + + + -tickleval + tickleval + 30 + 30 + (G) + - + + + -timeout + timeout + 4 + 4 + (G) + - + + + -sleep + sleep time + 10 + 10 + (G) + - + + + -dsireadbuf + dsireadbuf + 12 + 12 + (G) + - + + + -server_quantum + server quantum + 303840 + 303840 + (G) + - + + + -volnamelen + volnamelen + 80 + 80 + (G) + - + + + -setuplog + log level + default log_note + default:note + (G) + - + + + -setuplog + log file + - + - + (G) + - + + + -admingroup + admingroup + - + - + (G) + - + + + -k5service + k5 service + - + - + (G) + - + + + -k5realm + k5 realm + - + - + (G) + - + + + -k5keytab + k5 keytab + - + - + (G) + - + + + -uampath + uam path + etc/netatalk/uams/ + lib/netatalk/ + (G) + moved to $libdir + + + -ipaddr + afp listen + - + - + (G) + - + + + -cnidserver + cnid server + localhost:4700 + localhost:4700 + (G)/(V) + - + + + -port + port + 548 + 548 + (G) + - + + + -signature + signature + auto + - + (G) + - + + + -fqdn + fqdn + - + - + (G) + - + + + -unixcodepage + unix charset + LOCALE + UTF8 + (G) + - + + + -maccodepage + mac charset + MAC_ROMAN + MAC_ROMAN + (G)/(V) + - + + + -closevol + close vol + - + no + (G) + - + + + -ntdomain + nt domain + - + - + (G) + - + + + -ntseparator + nt separator + - + - + (G) + - + + + -dircachesize + dircachesize + 8192 + 8192 + (G) + - + + + -tcpsndbuf + tcpsndbuf + - + - + (G) + OS default + + + -tcprcvbuf + tcprcvbuf + - + - + (G) + OS default + + + -fcelistener + fce listener + - + - + (G) + - + + + -fcecoalesce + fce coalesce + - + - + (G) + - + + + -fceevents + fce events + - + - + (G) + - + + + -fceholdfmod + fce holdfmod + 60 + 60 + (G) + - + + + -mimicmodel + mimic model + - + - + (G) + - + + + -adminauthuser + admin auth user + - + - + (G) + - + + + -noacl2maccess + map acls + - + yes + (G) + - + + + -[no]tcp + - + -tcp + - + - + always TCP only + + + -[no]ddp + - + -noddp + - + - + AppleTalk is obsoleted + + + -[no]transall + - + -tcp -noddp + - + - + always TCP only + + + -nodebug + - + - + - + - + obsolete + + + -[no]slp + - + -noslp + - + - + SLP support is obsoleted + + + -[no]uservolfirst + - + -nouservolfirst + - + - + uservol is obsoleted + + + -[no]uservol + - + -uservol + - + - + uservol is obsoleted + + + -proxy + - + - + - + - + AppleTalk is obsoleted + + + -defaultvol + - + AppleVolumes.default + - + - + afp.conf only + + + -systemvol + - + AppleVolumes.system + - + - + afp.conf only + + + -loginmaxfail + - + - + - + - + not supported from the biginning + + + -unsetuplog + - + - + - + - + obsolete + + + -authprintdir + - + - + - + - + AppleTalk is obsoleted + + + -ddpaddr + - + - + - + - + AppleTalk is obsoleted + + + -[no]icon + - + -noicon + + - + obsolete + + + -keepsessions + - + - + - + - + obsolete. Use kill -HUP. + + + +
+ from afp_ldap.conf to afp.conf + + + + + + + + + + Old afp_ldap.conf + New afp.conf + Old Default Value + New Defalut Value + Section + Description + + + + + ldap_server + ldap server + - + - + (G) + - + + + ldap_auth_method + ldap auth method + - + - + (G) + - + + + ldap_auth_dn + ldap auth dn + - + - + (G) + - + + + ldap_auth_pw + ldap auth pw + - + - + (G) + - + + + ldap_userbase + ldap userbase + - + - + (G) + - + + + ldap_userscope + ldap userscope + - + - + (G) + - + + + ldap_groupbase + ldap groupbase + - + - + (G) + - + + + ldap_groupscope + ldap groupscope + - + - + (G) + - + + + ldap_uuid_attr + ldap uuid attr + - + - + (G) + - + + + ldap_uuid_string + ldap uuid string + - + - + (G) + - + + + ldap_name_attr + ldap name attr + - + - + (G) + - + + + ldap_group_attr + ldap group attr + - + - + (G) + - + + + +
+ from AppleVolumes.* to afp.conf + + + + + + + + + + Old AppleVolumes.* + New afp.conf + Old Default Value + New Defalut Value + Section + Description + + + + + (leading-dot lines) + - + - + - + - + move to extmap.conf + + + :DEFAULT: + - + options:upriv,usedots + - + - + use "vol preset =" + + + 1st field ("~") + - + - + - + - + use [Homes] section + + + 1st field ("/path") + path + - + - + (V) + - + + + 2nd field + - + - + - + - + use section name + + + allow: + valid users + - + - + (V) + - + + + deny: + invalid users + - + - + (V) + - + + + rwlist: + rwlist + - + - + (V) + - + + + rolist: + rolist + - + - + (V) + - + + + volcharset: + vol charset + UTF8 + (same as unix charset) + (G)/(V) + - + + + maccharset: + mac charset + MAC_ROMAN + MAC_ROMAN + (G)/(V) + - + + + veto: + veto files + - + - + (V) + - + + + cnidscheme: + cnid scheme + dbd + dbd + (V) + - + + + casefold: + casefold + - + - + (V) + - + + + adouble: + appledouble + v2 + ea + (V) + v1, osx and sfm are obsoleted + + + cnidserver: + cnid server + localhost:4700 + localhost:4700 + (G)/(V) + - + + + dbpath: + vol dbpath + (volume directory) + var/netatalk/CNID/ + (G) + moved to $localstatedir + + + umask: + umask + 0000 + 0000 + (V) + - + + + dperm: + directory perm + 0000 + 0000 + (V) + - + + + fperm: + file perm + 0000 + 0000 + (V) + - + + + password: + password + - + - + (V) + - + + + root_preexec: + root preexec + - + - + (V) + - + + + preexec: + preexec + - + - + (V) + - + + + root_postexec: + root postexec + - + - + (V) + - + + + postexec: + postexec + - + - + (V) + - + + + allowed_hosts: + hosts allow + - + - + (V) + - + + + denied_hosts: + hosts deny + - + - + (V) + - + + + ea: + ea + auto + auto + (V) + - + + + volsizelimit: + vol size limit + - + - + (V) + - + + + perm: + - + - + - + - + Use "directory perm" and "file perm" + + + forceuid: + - + - + - + - + obsolete + + + forcegid: + - + - + - + - + obsolete + + + options:ro + read only + - + no + (V) + - + + + options:invisibledots + invisible dots + - + no + (V) + - + + + options:nostat + stat vol + - + yes + (V) + - + + + options:preexec_close + preexec close + - + no + (V) + - + + + options:root_preexec_close + root preexec close + - + no + (V) + - + + + options:upriv + unix priv + - + yes + (V) + - + + + options:nodev + cnid dev + - + yes + (V) + - + + + options:illegalseq + illegal seq + - + no + (V) + - + + + options:tm + time machine + - + no + (V) + - + + + options:searchdb + search db + - + no + (V) + - + + + options:nonetids + network ids + - + yes + (V) + - + + + options:noacls + acls + - + yes + (V) + - + + + options:nohex + - + - + - + - + auto-convert from ":2f" to ":" + + + options:usedots + - + - + - + - + auto-convert from ":2e" to "." + + + options:nofileid + - + - + - + - + obsolete + + + options:prodos + - + - + - + - + obsolete + + + options:mswindows + - + - + - + - + obsolete + + + options:crlf + - + - + - + - + obsolete + + + options:noadouble + - + - + - + - + obsolete + + + options:limitsize + - + - + - + - + obsolete + + + options:dropbox + - + - + - + - + obsolete + + + options:dropkludge + - + - + - + - + obsolete + + + options:nocnidcache + - + - + - + - + obsolete + + + options:caseinsensitive + - + - + - + - + obsolete + + + +
+
+ + + To Do + + + + test ad utils with + + + +
diff --git a/doc/netatalk.html b/doc/netatalk.html new file mode 100644 index 00000000..b0b7904b --- /dev/null +++ b/doc/netatalk.html @@ -0,0 +1,14 @@ + +
+ diff --git a/doc/www/ReleaseNotes b/doc/www/ReleaseNotes new file mode 100644 index 00000000..ab0441a9 --- /dev/null +++ b/doc/www/ReleaseNotes @@ -0,0 +1,294 @@ +Netatalk 3.0.3 +============== + +The Netatalk development team is proud to announce version 3.0.3 of +the Netatalk File Sharing suite. This is the latest update to the 3.0 +release series. All users are encouraged to upgrade their systems to 3.0.3. + +Netatalk is a freely-available Open Source AFP fileserver. +A *NIX/*BSD system running Netatalk is capable of serving many Macintosh +clients simultaneously as an AppleShare file server (AFP). + +The suite contains: + +* netatalk - the main server service controller +* afpd - the AFP file server daemin +* cnid_metad - the CNID database multiplexing daemon +* cnid_dbd - the CNID database daemon serving CNIDs for AFP volumes +* various supporting programs and utilities + +Summary of major new features and enhancements in 3.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* New ini style configuration file afp.conf which replaces all previous + configuration files +* New default AppleDouble backend using filesystem Extended Attributes, + conversion from AppleDouble v2 is done automatically on access +* New service controller process "netatalk" responsible for starting and + restarting afpd and cnid_metad as necessary +* AppleTalk support has been removed +* Coherent cross-platform locking with Solaris CIFS server + +Please make sure to read the upgrading section in the Netatalk online +manual before trying to upgrade your system to 3.0! + + http://netatalk.sourceforge.net/3.0/htmldocs/upgrade.html + +License +~~~~~~~ + +Netatalk is a Free/Open Source Software project and is released under +the GNU General Public License (GPLv2). The full license text is available +at: + + http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt + +Changes in 3.0.3 +~~~~~~~~~~~~~~~~ +* UPD: afpd: Increase default DSI server quantum to 1 MB +* UPD: bundled libevent2 is now static +* NEW: --with-lockfile=PATH configure option for specifying an + alternative path for the netatalk lockfile. +* UPD: systemd service file use PIDFile and ExecReload. + From FR #70. +* UPD: RedHat sysvinit: rm graceful, reimplement reload, add condrestart +* FIX: Couldn't create folders on FreeBSD 9.1 ZFS fileystems. + Fixed bug #491. +* FIX: Fix an issue with user homes when user home directory has not the + same name as the username. + Fixes bug #497. +* UPD: Fix PAM config install, new default installation dir is + $sysconfdir/pam.d/. Add configure option --with-pam-confdir + to specify alternative path. +* NEW: AFP stats about active session via dbus IPC. Client side python + program `afpstats`. Requires dbus, dbus-glib any python-dbus. + configure option --dbus-sysconf-dir for specifying dbus + system security configuration files. + New option 'afpstats' (default: no) which determines whether + to enable the feature or not. +* NEW: configure option --with-init-dir +* NEW: dtrace probes, cf include/atalk/afp_dtrace.d for available + probes. +* UPD: Reload groups when reloading volumes. FR #71. +* FIX: Attempt to read read-only ._ rfork results in disconnect. + Fixes bug #502. +* FIX: File's ressource fork can't be read if metadata EA is missing. + Fixes bug #501. +* FIX: Conversion from adouble v2 to ea for directories. + Fixes bug #500. +* FIX: Error messages when mounting read-only filesystems. + Fixes bug #504. +* FIX: Permissions of ._ AppleDouble ressource fork after conversion + from v2 to ea. + Fixes bug #505. +* UPD: Use FreeBSD sendfile() capability to send protocol header. + From FR #75. +* UPD: Increase IO size when sendfile() is not used. + From FR #76. +* FIX: Can't set Finder label on symlinked folder with "follow symlinks = yes". + Fixes bug #508. +* FIX: Setting POSIX ACLs on Linux + Fixes bug #506. +* FIX: "ad ls" segfault if requested object is not in an AFP volume. + Fixes bug #496. + +Changes in 3.0.2 +~~~~~~~~~~~~~~~~ +* NEW: afpd: Put file extension type/creator mapping back in which had + been removed in 3.0. +* NEW: afpd: new option 'ad domain'. From FR #66. +* FIX: volumes and home share with symlinks in the path +* FIX: Copying packages to a Netatalk share could fail, bug #469 +* FIX: Reloading volumes from config file was broken. Fixes bug #474. +* FIX: Fix _device-info service type registered with dns-sd API +* FIX: Fix pathname bug for FCE modified event. +* FIX: Remove length limitation of options like "valid users". + Fixes bug #473. +* FIX: Dont copy our metadata EA in copyfile(). Fixes bug #452. +* FIX: Fix an error where catalog search gave incomplete results. + Fixes bug #479. +* REM: Remove TimeMachine volume used size FCE event. +* UPD: Add quoting support to '[in]valid users' option. Fixes bug #472. +* FIX: Install working PAM config on Solaris 11. Fixes bug #481. +* FIX: Fix a race condition between dbd and the cnid_dbd daemon + which could result in users being disconnected from volumes + when dbd was scanning their volumes. Fixes bug #477. +* FIX: Netatalk didn't start when the last line of the config file + afp.conf wasn't terminated by a newline. Fixes bug #476. +* NEW: Add a new volumes option 'follow symlinks'. The default setting is + false, symlinks are not followed on the server. This is the same + behaviour as OS X's AFP server. + Setting the option to true causes afpd to follow symlinks on the + server. symlinks may point outside of the AFP volume, currently + afpd doesn't do any checks for "wide symlinks". +* FIX: Automatic AppleDouble conversion to EAs failing for directories. + Fixes bug #486. +* FIX: dbd failed to convert appledouble files of symlinks. + Fixes bug #490. + +Changes in 3.0.1 +~~~~~~~~~~~~~~~~ +* NEW: afpd: Optional "ldap uuid encoding = string | ms-guid" parameter to + afp.conf, allowing for usage of the binary objectGUID fields from + Active Directory. +* FIX: afpd: Fix a Solaris 10 SPARC sendfilev bug +* FIX: afpd: Fix a crash on FreeBSD +* FIX: afpd: Fixes open file handle refcounting bug which was reported as + being unable to play movies off a Netatalk AFP share. + Bug ID 3559783. +* FIX: afpd: Fix a possible data corruption when reading from and writing + to the server simultaniously under load +* FIX: Fix possible alignment violations due to bad casts +* FIX: dbd: Fix logging +* FIX: apple_dump: Extended Attributes AppleDouble support for *BSD +* FIX: handling of '/' and ':' in volume name +* UPD: Install relevant includes necessary for building programs with + installed headers and shared lib libatalk +* UPD: libevent configure args to pick up installed version. Removed + configure arg --disable-libevent, added configure args + --with-libevent-header|lib. +* UPD: gentoo initscript: merge from portage netatalk.init,v 1.1 +* REM: Remove --with-smbsharemodes configure option, it was an + empty stub not yet implemented + +Changes in 3.0 +~~~~~~~~~~~~~~ + +* UPD: afpd: force read only mode if cnid scheme is last +* REM: afpd: removed global option "icon" +* FIX: CNID path for user homes + +Changes in 3.0 beta2 +~~~~~~~~~~~~~~~~~~~~ + +* UPD: Solaris and friends: Replace initscript with SMF manifest +* FIX: Solaris and friends: resource fork handling + +Changes in 3.0 beta1 +~~~~~~~~~~~~~~~~~~~~ + +* UPD: afpd: Performance tuning of read/write AFP operations. New option + "afp read locks" (default: no) which disables that the server + applies UNIX byte range locks to regions of files in AFP read and + write calls. +* UPD: apple_dump: Extended Attributes AppleDouble support. + (*BSD is not supported yet) + +Changes in 3.0 alpha3 +~~~~~~~~~~~~~~~~~~~~~ + +* NEW: afpd: Per volume "login message", NetAFP bug ID #18 +* NEW: afpd: Cross-platform locking (share modes) on Solaris and derivates + with Solaris CIFS/SMB server. Uses new Solaris fcntl F_SHARE share + reservation locking primitives. Enabled by default, set global + "solaris share reservations" option to false to disable it. +* NEW: ad: ad set subcommand for changing Mac metadata on the server +* UPD: unix charset is UTF8 by default + vol charset is same value as unix charset by default +* UPD: .AppleDesktop/ are stored in $localstatedir/netatalk/CNID + (default: /var/netatalk/CNID), databases found in AFP volumes are + automatically moved +* FIX: afpd: Server info packet was malformed resulting in broken + server names being displayed on clients +* FIX: afpd: Byte order detection. Fixes an error where Netatalk on + OpenIndiana returned wrong volume size information. + +Changes in 3.0 alpha2 +~~~~~~~~~~~~~~~~~~~~~ + +* UPD: afpd: Store '.' as is and '/' as ':' on the server, don't + CAP hexencode as "2e" and "2f" respectively +* UPD: afdp: Automatic name conversion, renaming files and directories + containing CAP sequences to their not enscaped forms +* UPD: afpd: Correct handling of user homes and users without homes +* UPD: afpd: Perform complete automatic adouble:v2 to adouble:ea conversion + as root. Previously only unlinking the adouble:v2 file was done as root +* UPD: dbd: -C option removes CAP encoding +* UPD: Add graceful option to RedHat init script +* UPD: Add --disable-bundled-libevent configure options When set to yes, + we rely on a properly installed version on libevent CPPFLAGS and LDFLAGS + should be set properly to pick that up +* UPD: Run ldconfig on Linux at the end of make install +* FIX: afpd: ad cp on appledouble = ea volumes +* FIX: dbd: ignore ._ appledouble files +* REM: Volumes options "use dots" and "hex encoding" + +Changes in 3.0 alpha1 +~~~~~~~~~~~~~~~~~~~~~ + +* NEW: Central configuration file afp.conf which replaces all previous files +* NEW: netatalk: service controller starting and restarting afpd and cnid_metad + as necessary +* NEW: afpd: Extended Attributes AppleDouble backend (default) +* UPD: CNID databases are stored in $localstatedir/netatalk/CNID + (default: /var/netatalk/CNID), databases found in AFP volumes are + automatically moved +* UPD: Start scripts and service manifests have been changed to only start + the new netatalk service controller process +* UPD: afpd: UNIX privileges and use dots enabled by default +* UPD: afpd: Support for arbitrary AFP volumes using variable expansion has been + removed +* UPD: afpd: afp_voluuid.conf and afp_signature.conf location has been + changed to $localstatedir/netatalk/ (default: /var/netatalk/) +* UPD: afpd: default server messages dir changed to $localstatedir/netatalk/msg/ +* UPD: dbd: new option -C for conversion from AppleDouble v2 to ea +* REM: AppleTalk support has been removed +* REM: afpd: SLP and AFP proxy support have been removed +* REM: afpd: legacy file extension to type/creator mapping has been removed +* REM: afpd: AppleDouble backends v1, osx and sfm have been removed + + +Supported Platforms +~~~~~~~~~~~~~~~~~~~ + +As of Netatalk 3.0 the following operating systems are supported: + + * FreeBSD + * Linux + * OpenBSD + * NetBSD + * Solaris and derivates + +Netatalk may compile and run on other operating systems as well, but +it is not well-tested on those. We welcome patches and suggestions +for enhancing the portability of Netatalk as well as success and failure +stories. Please write to netatalk-devel@lists.sourceforge.net. + +Availability +~~~~~~~~~~~~ + +Netatalk tar-balls can be found at: + +http://sourceforge.net/project/showfiles.php?group_id=8642 + +Netatalk is also available via anonymous git. See the SourceForge project +site for anonymous git instructions. + +Contact +~~~~~~~ + +For more information about Netatalk, see its web page at: + +http://netatalk.sourceforge.net/ + +The project is hosted at SourceForge. The SourceForge project page is +located at: + +http://sourceforge.net/projects/netatalk/ + +The Netatalk development team can be reached via the mailing list +netatalk-devel@lists.sourceforge.net. For subscription information and +archives see Netatalk's SourceForge project page. + +netatalk-admins@lists.sourceforge.net is a mailing list for Netatalk +system administrators. For subscription information and archives see +the Netatalk web page. + +Acknowledgements +~~~~~~~~~~~~~~~~ + +We would like to thank all contributors to the Netatalk project for +their commitment. Without the many suggestions, bug and problem reports, +patches, and reviews this project wouldn't be where it is. + + - The Netatalk Development Team, January 2013 diff --git a/doc/www/asciidoc.conf b/doc/www/asciidoc.conf new file mode 100644 index 00000000..85b3df4f --- /dev/null +++ b/doc/www/asciidoc.conf @@ -0,0 +1,610 @@ +# +# asciidoc.conf +# +# Asciidoc global configuration file. +# Contains backend independent configuration settings that are applied to all +# AsciiDoc documents. +# + +[miscellaneous] +tabsize=8 +textwidth=70 +newline=\r\n + +[attributes] +backend-alias-html=xhtml11 +backend-alias-docbook=docbook45 +toclevels=2 +sectids= +iconsdir=./images/icons +encoding=UTF-8 +# Uncomment to use xhtml11 quirks mode CSS. +#quirks= +# Uncomment to use the Pygments source highlighter instead of GNU highlighter. +#pygments= +# Uncomment to use deprecated quote attributes. +#deprecated-quotes= +empty= +# Attribute and AttributeList element patterns. +attributeentry-pattern=^:(?P\w[^.]*?)(\.(?P.*?))?:(\s+(?P.*))?$ +attributelist-pattern=(?u)(^\[\[(?P[\w_:][\w_:.-]*)(,(?P.*?))?\]\]$)|(^\[(?P.*)\]$) +# Substitution attributes for escaping AsciiDoc processing. +amp=& +lt=< +gt=> +brvbar=| +nbsp=  +zwsp=​ +wj=⁠ +deg=° +backslash=\ +two-colons=:: +two-semicolons=;; +# DEPRECATED: underscore attribute names. +two_colons=:: +two_semicolons=;; +# Left and right single and double quote characters. +# See http://en.wikipedia.org/wiki/Non-English_usage_of_quotation_marks +lsquo=‘ +rsquo=’ +ldquo=“ +rdquo=” + +[titles] +subs=specialcharacters,quotes,replacements,macros,attributes,replacements2 +# Double-line title pattern and underlines. +sectiontitle=^(?P.*?)$ +underlines="==","--","~~","^^","++" +# Single-line title patterns. +sect0=^= +(?P<title>[\S].*?)( +=)?$ +sect1=^== +(?P<title>[\S].*?)( +==)?$ +sect2=^=== +(?P<title>[\S].*?)( +===)?$ +sect3=^==== +(?P<title>[\S].*?)( +====)?$ +sect4=^===== +(?P<title>[\S].*?)( +=====)?$ +blocktitle=^\.(?P<title>([^.\s].*)|(\.[^.\s].*))$ + +[specialcharacters] +&=& +<=< +>=> + +[quotes] +# The order is important, quotes are processed in conf file order. +**=#strong +*=strong +``|''=doublequoted +'=emphasis +`|'=singlequoted +ifdef::no-inline-literal[] +`=monospaced +endif::no-inline-literal[] +# +++ and $$ quoting is applied to the +++ and $$ inline passthrough +# macros to allow quoted attributes to be used. +# This trick only works with inline passthrough macros. ++++=#unquoted +$$=#unquoted +++=#monospaced ++=monospaced +__=#emphasis +_=emphasis +\##=#unquoted +\#=unquoted +^=#superscript +~=#subscript + +[specialwords] +emphasizedwords= +strongwords= +monospacedwords= + +[replacements] +# Replacements performed in order of configuration file entry. The first entry +# of each replacement pair performs the (non-escaped) replacement, the second +# strips the backslash from the escaped replacement. + +# (C) Copyright (entity reference ©) +(?<!\\)\(C\)=© +\\\(C\)=(C) + +# (R) registered trade mark (entity reference ® +(?<!\\)\(R\)=® +\\\(R\)=(R) + +# (TM) Trademark (entity reference ™) +(?<!\\)\(TM\)=™ +\\\(TM\)=(TM) + +# -- Spaced and unspaced em dashes (entity reference —). +# Space on both sides is translated to thin space characters. +(^-- )=—  +(\n-- )|( -- )|( --\n)= —  +(\w)--(\w)=\1—\2 +\\--(?!-)=-- + +# Replace vertical typewriter apostrophe with punctuation apostrophe. +(\w)'(\w)=\1’\2 +(\w)\\'(\w)=\1'\2 + +# ... Ellipsis (entity reference …) +(?<!\\)\.\.\.=… +\\\.\.\.=... + +# Arrows from the Arrows block of Unicode. +# -> right arrow +(?<!\\)->=→ +\\->=-> +# => right double arrow +(?<!\\)\=>=⇒ +\\\=>==> +# <- left arrow +(?<!\\)<-=← +\\<-=<- +# <= left double arrow +(?<!\\)<\==⇐ +\\<\==<= + +# Arbitrary entity references. +(?<!\\)&([:_#a-zA-Z][:_.\-\w]*?;)=&\1 +\\(&[:_#a-zA-Z][:_.\-\w]*?;)=\1 + +#----------- +# Paragraphs +#----------- +[paradef-default] +delimiter=(?s)(?P<text>\S.*) +posattrs=style +style=normal +template::[paragraph-styles] + +[paradef-literal] +delimiter=(?s)(?P<text>\s+.*) +options=listelement +posattrs=style +style=literal +template::[paragraph-styles] + +[paradef-admonition] +delimiter=(?s)^\s*(?P<style>NOTE|TIP|IMPORTANT|WARNING|CAUTION):\s+(?P<text>.+) +template::[paragraph-styles] + +[paragraph-styles] +normal-style=template="paragraph" +verse-style=template="verseparagraph",posattrs=["style","attribution","citetitle"] +quote-style=template="quoteparagraph",posattrs=["style","attribution","citetitle"] +literal-style=template="literalparagraph",subs=["verbatim"] +listing-style=template="listingparagraph",subs=["verbatim"] +NOTE-style=template="admonitionparagraph",name="note",caption="{note-caption}" +TIP-style=template="admonitionparagraph",name="tip",caption="{tip-caption}" +IMPORTANT-style=template="admonitionparagraph",name="important",caption="{important-caption}" +WARNING-style=template="admonitionparagraph",name="warning",caption="{warning-caption}" +CAUTION-style=template="admonitionparagraph",name="caution",caption="{caution-caption}" + +[literalparagraph] +template::[literalblock] + +[verseparagraph] +template::[verseblock] + +[quoteparagraph] +template::[quoteblock] + +[listingparagraph] +template::[listingblock] + +[macros] +#-------------- +# Inline macros +#-------------- +# Backslash prefix required for escape processing. +# (?s) re flag for line spanning. + +# Macros using default syntax. +(?su)(?<!\w)[\\]?(?P<name>http|https|ftp|file|irc|mailto|callto|image|link|anchor|xref|indexterm):(?P<target>\S*?)\[(?P<attrlist>.*?)\]= + +# These URL types don't require any special attribute list formatting. +(?su)(?<!\S)[\\]?(?P<name>http|https|ftp|file|irc):(?P<target>//[^\s<>]*[\w/])= +# Allow a leading parenthesis and square bracket. +(?su)(?<\=[([])[\\]?(?P<name>http|https|ftp|file|irc):(?P<target>//[^\s<>]*[\w/])= +# Allow <> brackets. +(?su)[\\]?<(?P<name>http|https|ftp|file|irc):(?P<target>//[^\s<>]*[\w/])>= + +# Email addresses don't require special attribute list formatting. +# The before ">: and after "< character exclusions stop multiple substitution. +(?su)(?<![">:\w._/-])[\\]?(?P<target>\w[\w._-]*@[\w._-]*\w)(?!["<\w_-])=mailto + +# Allow footnote macros hard up against the preceding word so the footnote mark +# can be placed against the noted text without an intervening space +# (http://groups.google.com/group/asciidoc/browse_frm/thread/e1dcb7ee0efc17b5). +(?su)[\\]?(?P<name>footnote|footnoteref):(?P<target>\S*?)\[(?P<attrlist>.*?)\]= + +# Anchor: [[[id]]]. Bibliographic anchor. +(?su)[\\]?\[\[\[(?P<attrlist>[\w_:][\w_:.-]*?)\]\]\]=anchor3 +# Anchor: [[id,xreflabel]] +(?su)[\\]?\[\[(?P<attrlist>[\w"_:].*?)\]\]=anchor2 +# Link: <<id,text>> +(?su)[\\]?<<(?P<attrlist>[\w"_:].*?)>>=xref2 + +ifdef::asciidoc7compatible[] +# Index term: ++primary,secondary,tertiary++ +(?su)(?<!\S)[\\]?\+\+(?P<attrlist>[^+].*?)\+\+(?!\+)=indexterm +# Index term: +primary+ +# Follows ++...++ macro otherwise it will match them. +(?<!\S)[\\]?\+(?P<attrlist>[^\s\+][^+].*?)\+(?!\+)=indexterm2 +endif::asciidoc7compatible[] + +ifndef::asciidoc7compatible[] +# Index term: (((primary,secondary,tertiary))) +(?su)(?<!\()[\\]?\(\(\((?P<attrlist>[^(].*?)\)\)\)(?!\))=indexterm +# Index term: ((primary)) +# Follows (((...))) macro otherwise it will match them. +(?<!\()[\\]?\(\((?P<attrlist>[^\s\(][^(].*?)\)\)(?!\))=indexterm2 +endif::asciidoc7compatible[] + +# Callout +[\\]?<(?P<index>\d+)>=callout + +# Passthrough macros. +(?su)[\\]?(?P<name>pass):(?P<subslist>\S*?)\[(?P<passtext>.*?)(?<!\\)\]=[] + +# Triple-plus and double-dollar inline passthroughs. +(?su)[\\]?\+\+\+(?P<passtext>.*?)\+\+\+=pass[] +(?su)[\\]?\$\$(?P<passtext>.*?)\$\$=pass[specialcharacters] + +# Inline literal. +ifndef::no-inline-literal[] +(?su)(?<![`\w])([\\]?`(?P<passtext>[^`\s]|[^`\s].*?\S)`)(?![`\w])=literal[specialcharacters] +endif::no-inline-literal[] + +# Inline comment. +(?mu)^[\\]?//(?P<passtext>[^/].*|)$=comment[specialcharacters] + +# Default (catchall) inline macro is not implemented so there is no ambiguity +# with previous definition that could result in double substitution of escaped +# references. +#(?su)[\\]?(?P<name>\w(\w|-)*?):(?P<target>\S*?)\[(?P<passtext>.*?)(?<!\\)\]= + +#------------- +# Block macros +#------------- +# Macros using default syntax. +(?u)^(?P<name>image|unfloat)::(?P<target>\S*?)(\[(?P<attrlist>.*?)\])$=# + +# Passthrough macros. +(?u)^(?P<name>pass)::(?P<subslist>\S*?)(\[(?P<passtext>.*?)\])$=# + +^'{3,}$=#ruler +^<{3,}$=#pagebreak +^//(?P<passtext>[^/].*|)$=#comment[specialcharacters] + +[unfloat-blockmacro] +# Implemented in HTML backends. + +#----------------- +# Delimited blocks +#----------------- +[blockdef-comment] +delimiter=^/{4,}$ +options=skip + +[blockdef-sidebar] +delimiter=^\*{4,}$ +template=sidebarblock +options=sectionbody +posattrs=style +# DEPRECATED: Use Openblock instead. +abstract-style=template="abstractblock" + +[blockdef-open] +# A block without opening or closing tags. +delimiter=^--$ +template=openblock +options=sectionbody +posattrs=style +abstract-style=template="abstractblock" +partintro-style=template="partintroblock" + +[blockdef-pass] +delimiter=^\+{4,}$ +template=passblock +# Default subs choosen for backward compatibility. +subs=attributes,macros +posattrs=style +pass-style=template="passblock",subs=[] + +[blockdef-listing] +delimiter=^-{4,}$ +template=listingblock +subs=verbatim +posattrs=style + +[blockdef-literal] +delimiter=^\.{4,}$ +template=literalblock +subs=verbatim +posattrs=style +listing-style=template="listingblock" +# DEPRECATED: Use verse style on quote blocks instead. +verse-style=template="verseblock",subs="normal" + +[blockdef-quote] +delimiter=^_{4,}$ +subs=normal +style=quote +posattrs=style,attribution,citetitle +quote-style=template="quoteblock",options=("sectionbody",) +verse-style=template="verseblock" + +[blockdef-example] +delimiter=^={4,}$ +template=exampleblock +options=sectionbody +posattrs=style +NOTE-style=template="admonitionblock",name="note",caption="{note-caption}" +TIP-style=template="admonitionblock",name="tip",caption="{tip-caption}" +IMPORTANT-style=template="admonitionblock",name="important",caption="{important-caption}" +WARNING-style=template="admonitionblock",name="warning",caption="{warning-caption}" +CAUTION-style=template="admonitionblock",name="caution",caption="{caution-caption}" + +# For use by custom filters. +# DEPRECATED: No longer used, a styled listing block (blockdef-listing) is preferable. +[blockdef-filter] +delimiter=^~{4,}$ +template=listingblock +subs=none +posattrs=style + +#------- +# Lists +#------- +[listdef-bulleted] +# - bullets. +delimiter=^\s*- +(?P<text>.+)$ +posattrs=style +type=bulleted +tags=bulleted +callout-style=tags="callout" +bibliography-style=tags="bibliography" + +[listdef-bulleted1] +# * bullets. +template::[listdef-bulleted] +delimiter=^\s*\* +(?P<text>.+)$ + +[listdef-bulleted2] +# ** bullets. +template::[listdef-bulleted] +delimiter=^\s*\*{2} +(?P<text>.+)$ + +[listdef-bulleted3] +# *** bullets. +template::[listdef-bulleted] +delimiter=^\s*\*{3} +(?P<text>.+)$ + +[listdef-bulleted4] +# **** bullets. +template::[listdef-bulleted] +delimiter=^\s*\*{4} +(?P<text>.+)$ + +[listdef-bulleted5] +# ***** bullets. +template::[listdef-bulleted] +delimiter=^\s*\*{5} +(?P<text>.+)$ + +[listdef-arabic] +# Arabic numbering. +delimiter=^\s*(?P<index>\d+\.) +(?P<text>.+)$ +posattrs=style +type=numbered +tags=numbered +style=arabic + +[listdef-loweralpha] +# Lower alpha numbering. +template::[listdef-arabic] +delimiter=^\s*(?P<index>[a-z]\.) +(?P<text>.+)$ +style=loweralpha + +[listdef-upperalpha] +# Upper alpha numbering. +template::[listdef-arabic] +delimiter=^\s*(?P<index>[A-Z]\.) +(?P<text>.+)$ +style=upperalpha + +[listdef-lowerroman] +# Lower roman numbering. +template::[listdef-arabic] +delimiter=^\s*(?P<index>[ivx]+\)) +(?P<text>.+)$ +style=lowerroman + +[listdef-upperroman] +# Upper roman numbering. +template::[listdef-arabic] +delimiter=^\s*(?P<index>[IVX]+\)) +(?P<text>.+)$ +style=upperroman + +[listdef-numbered1] +# . numbering. +template::[listdef-arabic] +delimiter=^\s*\. +(?P<text>.+)$ + +[listdef-numbered2] +# .. numbering. +template::[listdef-loweralpha] +delimiter=^\s*\.{2} +(?P<text>.+)$ + +[listdef-numbered3] +# ... numbering. +template::[listdef-lowerroman] +delimiter=^\s*\.{3} +(?P<text>.+)$ + +[listdef-numbered4] +# .... numbering. +template::[listdef-upperalpha] +delimiter=^\s*\.{4} +(?P<text>.+)$ + +[listdef-numbered5] +# ..... numbering. +template::[listdef-upperroman] +delimiter=^\s*\.{5} +(?P<text>.+)$ + +[listdef-labeled] +# label:: item. +delimiter=^\s*(?P<label>.*[^:])::(\s+(?P<text>.+))?$ +posattrs=style +type=labeled +tags=labeled +vertical-style=tags="labeled" +horizontal-style=tags="horizontal" +glossary-style=tags="glossary" +qanda-style=tags="qanda" + +[listdef-labeled2] +# label;; item. +template::[listdef-labeled] +delimiter=^\s*(?P<label>.*[^;]);;(\s+(?P<text>.+))?$ + +[listdef-labeled3] +# label::: item. +template::[listdef-labeled] +delimiter=^\s*(?P<label>.*[^:]):{3}(\s+(?P<text>.+))?$ + +[listdef-labeled4] +# label:::: item. +template::[listdef-labeled] +delimiter=^\s*(?P<label>.*[^:]):{4}(\s+(?P<text>.+))?$ + +[listdef-callout] +posattrs=style +delimiter=^<?(?P<index>\d*>) +(?P<text>.+)$ +type=callout +tags=callout +style=arabic + +# DEPRECATED: Old list syntax. +[listdef-qanda] +posattrs=style +delimiter=^\s*(?P<label>.*\S)\?\?$ +type=labeled +tags=qanda + +# DEPRECATED: Old list syntax. +[listdef-bibliography] +posattrs=style +delimiter=^\+ +(?P<text>.+)$ +type=bulleted +tags=bibliography + +# DEPRECATED: Old list syntax. +[listdef-glossary] +delimiter=^(?P<label>.*\S):-$ +posattrs=style +type=labeled +tags=glossary + +#------- +# Tables +#------- +[tabledef-default] +delimiter=^\|={3,}$ +posattrs=style +template=table +default-style=tags="default" +verse-style=tags="verse" +literal-style=tags="literal",subs=["specialcharacters"] +emphasis-style=tags="emphasis" +strong-style=tags="strong" +monospaced-style=tags="monospaced" +header-style=tags="header" +asciidoc-style=tags="asciidoc",subs=[],filter='python "{asciidoc-file}" -b {backend} {asciidoc-args}{lang? -a "lang={lang}@"}{icons? -a icons -a "iconsdir={iconsdir}"}{imagesdir? -a "imagesdir={imagesdir}"}{data-uri? -a data-uri} -a "indir={indir}"{trace? -a "trace={trace}"} -s -' + +[tabledef-nested] +# Same as [tabledef-default] but with different delimiter and separator. +delimiter=^!={3,}$ +separator=((?<!\S)((?P<span>[\d.]+)(?P<op>[*+]))?(?P<align>[<\^>.]{,3})?(?P<style>[a-z])?)?! +posattrs=style +template=table +verse-style=tags="verse" +literal-style=tags="literal",subs=["specialcharacters"] +emphasis-style=tags="emphasis" +strong-style=tags="strong" +monospaced-style=tags="monospaced" +header-style=tags="header" +asciidoc-style=tags="asciidoc",subs=[],filter='python "{asciidoc-file}" -b {backend} {asciidoc-args}{lang? -a "lang={lang}@"} -s -' + +#---------------------------------------- +# Common block and macro markup templates +#---------------------------------------- +[comment-inlinemacro] +# Outputs nothing. + +[comment-blockmacro] +# Outputs nothing. + +[pass-blockmacro] +{passtext} + +[pass-inlinemacro] +template::[pass-blockmacro] + +[passblock] +| + +[filter-image-blockmacro] +# Synthesize missing target attribute for filter generated file names. +# The tag split | ensures missing target file names are auto-generated +# before the filter is executed, the remainder (the [image-blockmacro]) +# is excuted after the filter to ensure data URI encoding comes after +# the image is created. +{target%}{counter2:target-number} +{target%}{set2:target:{docname}__{target-number}.png} +| +template::[image-blockmacro] + +#---------------------------------- +# Default special section templates +#---------------------------------- +[abstract] +template::[sect1] + +[colophon] +template::[sect1] + +[dedication] +template::[sect1] + +[preface] +template::[sect1] + +[appendix] +template::[sect1] + +[glossary] +template::[sect1] + +[bibliography] +template::[sect1] + +[index] +template::[sect1] + +[synopsis] +template::[sect1] + +#-------------------------------------------------------------------- +# Deprecated old table definitions. +# + +[old_tabledef-default] +fillchar=- +format=fixed + +[old_tabledef-csv] +fillchar=~ +format=csv + +[old_tabledef-dsv] +fillchar=_ +format=dsv + +# End of deprecated old table definitions. +#-------------------------------------------------------------------- diff --git a/doc/www/asciidoc.py b/doc/www/asciidoc.py new file mode 100755 index 00000000..7846de34 --- /dev/null +++ b/doc/www/asciidoc.py @@ -0,0 +1,5902 @@ +#!/usr/bin/env python +""" +asciidoc - converts an AsciiDoc text file to HTML or DocBook + +Copyright (C) 2002-2010 Stuart Rackham. Free use of this software is granted +under the terms of the GNU General Public License (GPL). +""" + +import sys, os, re, time, traceback, tempfile, subprocess, codecs, locale, unicodedata + +### Used by asciidocapi.py ### +VERSION = '8.6.5' # See CHANGLOG file for version history. + +MIN_PYTHON_VERSION = 2.4 # Require this version of Python or better. + +#--------------------------------------------------------------------------- +# Program constants. +#--------------------------------------------------------------------------- +DEFAULT_BACKEND = 'html' +DEFAULT_DOCTYPE = 'article' +# Allowed substitution options for List, Paragraph and DelimitedBlock +# definition subs entry. +SUBS_OPTIONS = ('specialcharacters','quotes','specialwords', + 'replacements', 'attributes','macros','callouts','normal','verbatim', + 'none','replacements2') +# Default value for unspecified subs and presubs configuration file entries. +SUBS_NORMAL = ('specialcharacters','quotes','attributes', + 'specialwords','replacements','macros','replacements2') +SUBS_VERBATIM = ('specialcharacters','callouts') + +NAME_RE = r'(?u)[^\W\d][-\w]*' # Valid section or attribute name. +OR, AND = ',', '+' # Attribute list separators. + + +#--------------------------------------------------------------------------- +# Utility functions and classes. +#--------------------------------------------------------------------------- + +class EAsciiDoc(Exception): pass + +class OrderedDict(dict): + """ + Dictionary ordered by insertion order. + Python Cookbook: Ordered Dictionary, Submitter: David Benjamin. + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 + """ + def __init__(self, d=None, **kwargs): + self._keys = [] + if d is None: d = kwargs + dict.__init__(self, d) + def __delitem__(self, key): + dict.__delitem__(self, key) + self._keys.remove(key) + def __setitem__(self, key, item): + dict.__setitem__(self, key, item) + if key not in self._keys: self._keys.append(key) + def clear(self): + dict.clear(self) + self._keys = [] + def copy(self): + d = dict.copy(self) + d._keys = self._keys[:] + return d + def items(self): + return zip(self._keys, self.values()) + def keys(self): + return self._keys + def popitem(self): + try: + key = self._keys[-1] + except IndexError: + raise KeyError('dictionary is empty') + val = self[key] + del self[key] + return (key, val) + def setdefault(self, key, failobj = None): + dict.setdefault(self, key, failobj) + if key not in self._keys: self._keys.append(key) + def update(self, d=None, **kwargs): + if d is None: + d = kwargs + dict.update(self, d) + for key in d.keys(): + if key not in self._keys: self._keys.append(key) + def values(self): + return map(self.get, self._keys) + +class AttrDict(dict): + """ + Like a dictionary except values can be accessed as attributes i.e. obj.foo + can be used in addition to obj['foo']. + If an item is not present None is returned. + """ + def __getattr__(self, key): + try: return self[key] + except KeyError: return None + def __setattr__(self, key, value): + self[key] = value + def __delattr__(self, key): + try: del self[key] + except KeyError, k: raise AttributeError, k + def __repr__(self): + return '<AttrDict ' + dict.__repr__(self) + '>' + def __getstate__(self): + return dict(self) + def __setstate__(self,value): + for k,v in value.items(): self[k]=v + +class InsensitiveDict(dict): + """ + Like a dictionary except key access is case insensitive. + Keys are stored in lower case. + """ + def __getitem__(self, key): + return dict.__getitem__(self, key.lower()) + def __setitem__(self, key, value): + dict.__setitem__(self, key.lower(), value) + def has_key(self, key): + return dict.has_key(self,key.lower()) + def get(self, key, default=None): + return dict.get(self, key.lower(), default) + def update(self, dict): + for k,v in dict.items(): + self[k] = v + def setdefault(self, key, default = None): + return dict.setdefault(self, key.lower(), default) + + +class Trace(object): + """ + Used in conjunction with the 'trace' attribute to generate diagnostic + output. There is a single global instance of this class named trace. + """ + SUBS_NAMES = ('specialcharacters','quotes','specialwords', + 'replacements', 'attributes','macros','callouts', + 'replacements2') + def __init__(self): + self.name_re = '' # Regexp pattern to match trace names. + self.linenos = True + self.offset = 0 + def __call__(self, name, before, after=None): + """ + Print trace message if tracing is on and the trace 'name' matches the + document 'trace' attribute (treated as a regexp). + 'before' is the source text before substitution; 'after' text is the + source text after substitutuion. + The 'before' and 'after' messages are only printed if they differ. + """ + name_re = document.attributes.get('trace') + if name_re == 'subs': # Alias for all the inline substitutions. + name_re = '|'.join(self.SUBS_NAMES) + self.name_re = name_re + if self.name_re is not None: + msg = message.format(name, 'TRACE: ', self.linenos, offset=self.offset) + if before != after and re.match(self.name_re,name): + if is_array(before): + before = '\n'.join(before) + if after is None: + msg += '\n%s\n' % before + else: + if is_array(after): + after = '\n'.join(after) + msg += '\n<<<\n%s\n>>>\n%s\n' % (before,after) + message.stderr(msg) + +class Message: + """ + Message functions. + """ + PROG = os.path.basename(os.path.splitext(__file__)[0]) + + def __init__(self): + # Set to True or False to globally override line numbers method + # argument. Has no effect when set to None. + self.linenos = None + self.messages = [] + + def stdout(self,msg): + print msg + + def stderr(self,msg=''): + self.messages.append(msg) + if __name__ == '__main__': + sys.stderr.write('%s: %s%s' % (self.PROG, msg, os.linesep)) + + def verbose(self, msg,linenos=True): + if config.verbose: + msg = self.format(msg,linenos=linenos) + self.stderr(msg) + + def warning(self, msg,linenos=True,offset=0): + msg = self.format(msg,'WARNING: ',linenos,offset=offset) + document.has_warnings = True + self.stderr(msg) + + def deprecated(self, msg, linenos=True): + msg = self.format(msg, 'DEPRECATED: ', linenos) + self.stderr(msg) + + def format(self, msg, prefix='', linenos=True, cursor=None, offset=0): + """Return formatted message string.""" + if self.linenos is not False and ((linenos or self.linenos) and reader.cursor): + if cursor is None: + cursor = reader.cursor + prefix += '%s: line %d: ' % (os.path.basename(cursor[0]),cursor[1]+offset) + return prefix + msg + + def error(self, msg, cursor=None, halt=False): + """ + Report fatal error. + If halt=True raise EAsciiDoc exception. + If halt=False don't exit application, continue in the hope of reporting + all fatal errors finishing with a non-zero exit code. + """ + if halt: + raise EAsciiDoc, self.format(msg,linenos=False,cursor=cursor) + else: + msg = self.format(msg,'ERROR: ',cursor=cursor) + self.stderr(msg) + document.has_errors = True + + def unsafe(self, msg): + self.error('unsafe: '+msg) + + +def userdir(): + """ + Return user's home directory or None if it is not defined. + """ + result = os.path.expanduser('~') + if result == '~': + result = None + return result + +def localapp(): + """ + Return True if we are not executing the system wide version + i.e. the configuration is in the executable's directory. + """ + return os.path.isfile(os.path.join(APP_DIR, 'asciidoc.conf')) + +def file_in(fname, directory): + """Return True if file fname resides inside directory.""" + assert os.path.isfile(fname) + # Empty directory (not to be confused with None) is the current directory. + if directory == '': + directory = os.getcwd() + else: + assert os.path.isdir(directory) + directory = os.path.realpath(directory) + fname = os.path.realpath(fname) + return os.path.commonprefix((directory, fname)) == directory + +def safe(): + return document.safe + +def is_safe_file(fname, directory=None): + # A safe file must reside in directory directory (defaults to the source + # file directory). + if directory is None: + if document.infile == '<stdin>': + return not safe() + directory = os.path.dirname(document.infile) + elif directory == '': + directory = '.' + return ( + not safe() + or file_in(fname, directory) + or file_in(fname, APP_DIR) + or file_in(fname, CONF_DIR) + ) + +def safe_filename(fname, parentdir): + """ + Return file name which must reside in the parent file directory. + Return None if file is not found or not safe. + """ + if not os.path.isabs(fname): + # Include files are relative to parent document + # directory. + fname = os.path.normpath(os.path.join(parentdir,fname)) + if not os.path.isfile(fname): + message.warning('include file not found: %s' % fname) + return None + if not is_safe_file(fname, parentdir): + message.unsafe('include file: %s' % fname) + return None + return fname + +def assign(dst,src): + """Assign all attributes from 'src' object to 'dst' object.""" + for a,v in src.__dict__.items(): + setattr(dst,a,v) + +def strip_quotes(s): + """Trim white space and, if necessary, quote characters from s.""" + s = s.strip() + # Strip quotation mark characters from quoted strings. + if len(s) >= 3 and s[0] == '"' and s[-1] == '"': + s = s[1:-1] + return s + +def is_re(s): + """Return True if s is a valid regular expression else return False.""" + try: re.compile(s) + except: return False + else: return True + +def re_join(relist): + """Join list of regular expressions re1,re2,... to single regular + expression (re1)|(re2)|...""" + if len(relist) == 0: + return None + result = [] + # Delete named groups to avoid ambiguity. + for s in relist: + result.append(re.sub(r'\?P<\S+?>','',s)) + result = ')|('.join(result) + result = '('+result+')' + return result + +def validate(value,rule,errmsg): + """Validate value against rule expression. Throw EAsciiDoc exception with + errmsg if validation fails.""" + try: + if not eval(rule.replace('$',str(value))): + raise EAsciiDoc,errmsg + except Exception: + raise EAsciiDoc,errmsg + return value + +def lstrip_list(s): + """ + Return list with empty items from start of list removed. + """ + for i in range(len(s)): + if s[i]: break + else: + return [] + return s[i:] + +def rstrip_list(s): + """ + Return list with empty items from end of list removed. + """ + for i in range(len(s)-1,-1,-1): + if s[i]: break + else: + return [] + return s[:i+1] + +def strip_list(s): + """ + Return list with empty items from start and end of list removed. + """ + s = lstrip_list(s) + s = rstrip_list(s) + return s + +def is_array(obj): + """ + Return True if object is list or tuple type. + """ + return isinstance(obj,list) or isinstance(obj,tuple) + +def dovetail(lines1, lines2): + """ + Append list or tuple of strings 'lines2' to list 'lines1'. Join the last + non-blank item in 'lines1' with the first non-blank item in 'lines2' into a + single string. + """ + assert is_array(lines1) + assert is_array(lines2) + lines1 = strip_list(lines1) + lines2 = strip_list(lines2) + if not lines1 or not lines2: + return list(lines1) + list(lines2) + result = list(lines1[:-1]) + result.append(lines1[-1] + lines2[0]) + result += list(lines2[1:]) + return result + +def dovetail_tags(stag,content,etag): + """Merge the end tag with the first content line and the last + content line with the end tag. This ensures verbatim elements don't + include extraneous opening and closing line breaks.""" + return dovetail(dovetail(stag,content), etag) + +def parse_attributes(attrs,dict): + """Update a dictionary with name/value attributes from the attrs string. + The attrs string is a comma separated list of values and keyword name=value + pairs. Values must preceed keywords and are named '1','2'... The entire + attributes list is named '0'. If keywords are specified string values must + be quoted. Examples: + + attrs: '' + dict: {} + + attrs: 'hello,world' + dict: {'2': 'world', '0': 'hello,world', '1': 'hello'} + + attrs: '"hello", planet="earth"' + dict: {'planet': 'earth', '0': '"hello",planet="earth"', '1': 'hello'} + """ + def f(*args,**keywords): + # Name and add aguments '1','2'... to keywords. + for i in range(len(args)): + if not str(i+1) in keywords: + keywords[str(i+1)] = args[i] + return keywords + + if not attrs: + return + dict['0'] = attrs + # Replace line separators with spaces so line spanning works. + s = re.sub(r'\s', ' ', attrs) + try: + d = eval('f('+s+')') + # Attributes must evaluate to strings, numbers or None. + for v in d.values(): + if not (isinstance(v,str) or isinstance(v,int) or isinstance(v,float) or v is None): + raise Exception + except Exception: + s = s.replace('"','\\"') + s = s.split(',') + s = map(lambda x: '"' + x.strip() + '"', s) + s = ','.join(s) + try: + d = eval('f('+s+')') + except Exception: + return # If there's a syntax error leave with {0}=attrs. + for k in d.keys(): # Drop any empty positional arguments. + if d[k] == '': del d[k] + dict.update(d) + assert len(d) > 0 + +def parse_named_attributes(s,attrs): + """Update a attrs dictionary with name="value" attributes from the s string. + Returns False if invalid syntax. + Example: + attrs: 'star="sun",planet="earth"' + dict: {'planet':'earth', 'star':'sun'} + """ + def f(**keywords): return keywords + + try: + d = eval('f('+s+')') + attrs.update(d) + return True + except Exception: + return False + +def parse_list(s): + """Parse comma separated string of Python literals. Return a tuple of of + parsed values.""" + try: + result = eval('tuple(['+s+'])') + except Exception: + raise EAsciiDoc,'malformed list: '+s + return result + +def parse_options(options,allowed,errmsg): + """Parse comma separated string of unquoted option names and return as a + tuple of valid options. 'allowed' is a list of allowed option values. + If allowed=() then all legitimate names are allowed. + 'errmsg' is an error message prefix if an illegal option error is thrown.""" + result = [] + if options: + for s in re.split(r'\s*,\s*',options): + if (allowed and s not in allowed) or not is_name(s): + raise EAsciiDoc,'%s: %s' % (errmsg,s) + result.append(s) + return tuple(result) + +def symbolize(s): + """Drop non-symbol characters and convert to lowercase.""" + return re.sub(r'(?u)[^\w\-_]', '', s).lower() + +def is_name(s): + """Return True if s is valid attribute, macro or tag name + (starts with alpha containing alphanumeric and dashes only).""" + return re.match(r'^'+NAME_RE+r'$',s) is not None + +def subs_quotes(text): + """Quoted text is marked up and the resulting text is + returned.""" + keys = config.quotes.keys() + for q in keys: + i = q.find('|') + if i != -1 and q != '|' and q != '||': + lq = q[:i] # Left quote. + rq = q[i+1:] # Right quote. + else: + lq = rq = q + tag = config.quotes[q] + if not tag: continue + # Unconstrained quotes prefix the tag name with a hash. + if tag[0] == '#': + tag = tag[1:] + # Unconstrained quotes can appear anywhere. + reo = re.compile(r'(?msu)(^|.)(\[(?P<attrlist>[^[\]]+?)\])?' \ + + r'(?:' + re.escape(lq) + r')' \ + + r'(?P<content>.+?)(?:'+re.escape(rq)+r')') + else: + # The text within constrained quotes must be bounded by white space. + # Non-word (\W) characters are allowed at boundaries to accomodate + # enveloping quotes and punctuation e.g. a='x', ('x'), 'x', ['x']. + reo = re.compile(r'(?msu)(^|[^\w;:}])(\[(?P<attrlist>[^[\]]+?)\])?' \ + + r'(?:' + re.escape(lq) + r')' \ + + r'(?P<content>\S|\S.*?\S)(?:'+re.escape(rq)+r')(?=\W|$)') + pos = 0 + while True: + mo = reo.search(text,pos) + if not mo: break + if text[mo.start()] == '\\': + # Delete leading backslash. + text = text[:mo.start()] + text[mo.start()+1:] + # Skip past start of match. + pos = mo.start() + 1 + else: + attrlist = {} + parse_attributes(mo.group('attrlist'), attrlist) + stag,etag = config.tag(tag, attrlist) + s = mo.group(1) + stag + mo.group('content') + etag + text = text[:mo.start()] + s + text[mo.end():] + pos = mo.start() + len(s) + return text + +def subs_tag(tag,dict={}): + """Perform attribute substitution and split tag string returning start, end + tag tuple (c.f. Config.tag()).""" + if not tag: + return [None,None] + s = subs_attrs(tag,dict) + if not s: + message.warning('tag \'%s\' dropped: contains undefined attribute' % tag) + return [None,None] + result = s.split('|') + if len(result) == 1: + return result+[None] + elif len(result) == 2: + return result + else: + raise EAsciiDoc,'malformed tag: %s' % tag + +def parse_entry(entry, dict=None, unquote=False, unique_values=False, + allow_name_only=False, escape_delimiter=True): + """Parse name=value entry to dictionary 'dict'. Return tuple (name,value) + or None if illegal entry. + If name= then value is set to ''. + If name and allow_name_only=True then value is set to ''. + If name! and allow_name_only=True then value is set to None. + Leading and trailing white space is striped from 'name' and 'value'. + 'name' can contain any printable characters. + If the '=' delimiter character is allowed in the 'name' then + it must be escaped with a backslash and escape_delimiter must be True. + If 'unquote' is True leading and trailing double-quotes are stripped from + 'name' and 'value'. + If unique_values' is True then dictionary entries with the same value are + removed before the parsed entry is added.""" + if escape_delimiter: + mo = re.search(r'(?:[^\\](=))',entry) + else: + mo = re.search(r'(=)',entry) + if mo: # name=value entry. + if mo.group(1): + name = entry[:mo.start(1)] + if escape_delimiter: + name = name.replace(r'\=','=') # Unescape \= in name. + value = entry[mo.end(1):] + elif allow_name_only and entry: # name or name! entry. + name = entry + if name[-1] == '!': + name = name[:-1] + value = None + else: + value = '' + else: + return None + if unquote: + name = strip_quotes(name) + if value is not None: + value = strip_quotes(value) + else: + name = name.strip() + if value is not None: + value = value.strip() + if not name: + return None + if dict is not None: + if unique_values: + for k,v in dict.items(): + if v == value: del dict[k] + dict[name] = value + return name,value + +def parse_entries(entries, dict, unquote=False, unique_values=False, + allow_name_only=False,escape_delimiter=True): + """Parse name=value entries from from lines of text in 'entries' into + dictionary 'dict'. Blank lines are skipped.""" + entries = config.expand_templates(entries) + for entry in entries: + if entry and not parse_entry(entry, dict, unquote, unique_values, + allow_name_only, escape_delimiter): + raise EAsciiDoc,'malformed section entry: %s' % entry + +def dump_section(name,dict,f=sys.stdout): + """Write parameters in 'dict' as in configuration file section format with + section 'name'.""" + f.write('[%s]%s' % (name,writer.newline)) + for k,v in dict.items(): + k = str(k) + k = k.replace('=',r'\=') # Escape = in name. + # Quote if necessary. + if len(k) != len(k.strip()): + k = '"'+k+'"' + if v and len(v) != len(v.strip()): + v = '"'+v+'"' + if v is None: + # Don't dump undefined attributes. + continue + else: + s = k+'='+v + if s[0] == '#': + s = '\\' + s # Escape so not treated as comment lines. + f.write('%s%s' % (s,writer.newline)) + f.write(writer.newline) + +def update_attrs(attrs,dict): + """Update 'attrs' dictionary with parsed attributes in dictionary 'dict'.""" + for k,v in dict.items(): + if not is_name(k): + raise EAsciiDoc,'illegal attribute name: %s' % k + attrs[k] = v + +def is_attr_defined(attrs,dic): + """ + Check if the sequence of attributes is defined in dictionary 'dic'. + Valid 'attrs' sequence syntax: + <attr> Return True if single attrbiute is defined. + <attr1>,<attr2>,... Return True if one or more attributes are defined. + <attr1>+<attr2>+... Return True if all the attributes are defined. + """ + if OR in attrs: + for a in attrs.split(OR): + if dic.get(a.strip()) is not None: + return True + else: return False + elif AND in attrs: + for a in attrs.split(AND): + if dic.get(a.strip()) is None: + return False + else: return True + else: + return dic.get(attrs.strip()) is not None + +def filter_lines(filter_cmd, lines, attrs={}): + """ + Run 'lines' through the 'filter_cmd' shell command and return the result. + The 'attrs' dictionary contains additional filter attributes. + """ + def findfilter(name,dir,filter): + """Find filter file 'fname' with style name 'name' in directory + 'dir'. Return found file path or None if not found.""" + if name: + result = os.path.join(dir,'filters',name,filter) + if os.path.isfile(result): + return result + result = os.path.join(dir,'filters',filter) + if os.path.isfile(result): + return result + return None + + # Return input lines if there's not filter. + if not filter_cmd or not filter_cmd.strip(): + return lines + # Perform attributes substitution on the filter command. + s = subs_attrs(filter_cmd, attrs) + if not s: + message.error('undefined filter attribute in command: %s' % filter_cmd) + return [] + filter_cmd = s.strip() + # Parse for quoted and unquoted command and command tail. + # Double quoted. + mo = re.match(r'^"(?P<cmd>[^"]+)"(?P<tail>.*)$', filter_cmd) + if not mo: + # Single quoted. + mo = re.match(r"^'(?P<cmd>[^']+)'(?P<tail>.*)$", filter_cmd) + if not mo: + # Unquoted catch all. + mo = re.match(r'^(?P<cmd>\S+)(?P<tail>.*)$', filter_cmd) + cmd = mo.group('cmd').strip() + found = None + if not os.path.dirname(cmd): + # Filter command has no directory path so search filter directories. + filtername = attrs.get('style') + d = document.attributes.get('docdir') + if d: + found = findfilter(filtername, d, cmd) + if not found: + if USER_DIR: + found = findfilter(filtername, USER_DIR, cmd) + if not found: + if localapp(): + found = findfilter(filtername, APP_DIR, cmd) + else: + found = findfilter(filtername, CONF_DIR, cmd) + else: + if os.path.isfile(cmd): + found = cmd + else: + message.warning('filter not found: %s' % cmd) + if found: + filter_cmd = '"' + found + '"' + mo.group('tail') + if sys.platform == 'win32': + # Windows doesn't like running scripts directly so explicitly + # specify interpreter. + if found: + if cmd.endswith('.py'): + filter_cmd = 'python ' + filter_cmd + elif cmd.endswith('.rb'): + filter_cmd = 'ruby ' + filter_cmd + message.verbose('filtering: ' + filter_cmd) + try: + p = subprocess.Popen(filter_cmd, shell=True, + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + output = p.communicate(os.linesep.join(lines))[0] + except Exception: + raise EAsciiDoc,'filter error: %s: %s' % (filter_cmd, sys.exc_info()[1]) + if output: + result = [s.rstrip() for s in output.split(os.linesep)] + else: + result = [] + filter_status = p.wait() + if filter_status: + message.warning('filter non-zero exit code: %s: returned %d' % + (filter_cmd, filter_status)) + if lines and not result: + message.warning('no output from filter: %s' % filter_cmd) + return result + +def system(name, args, is_macro=False, attrs=None): + """ + Evaluate a system attribute ({name:args}) or system block macro + (name::[args]). + If is_macro is True then we are processing a system block macro otherwise + it's a system attribute. + The attrs dictionary is updated by the counter and set system attributes. + NOTE: The include1 attribute is used internally by the include1::[] macro + and is not for public use. + """ + if is_macro: + syntax = '%s::[%s]' % (name,args) + separator = '\n' + else: + syntax = '{%s:%s}' % (name,args) + separator = writer.newline + if name not in ('eval','eval3','sys','sys2','sys3','include','include1','counter','counter2','set','set2','template'): + if is_macro: + msg = 'illegal system macro name: %s' % name + else: + msg = 'illegal system attribute name: %s' % name + message.warning(msg) + return None + if is_macro: + s = subs_attrs(args) + if s is None: + message.warning('skipped %s: undefined attribute in: %s' % (name,args)) + return None + args = s + if name != 'include1': + message.verbose('evaluating: %s' % syntax) + if safe() and name not in ('include','include1'): + message.unsafe(syntax) + return None + result = None + if name in ('eval','eval3'): + try: + result = eval(args) + if result is True: + result = '' + elif result is False: + result = None + elif result is not None: + result = str(result) + except Exception: + message.warning('%s: evaluation error' % syntax) + elif name in ('sys','sys2','sys3'): + result = '' + fd,tmp = tempfile.mkstemp() + os.close(fd) + try: + cmd = args + cmd = cmd + (' > %s' % tmp) + if name == 'sys2': + cmd = cmd + ' 2>&1' + if os.system(cmd): + message.warning('%s: non-zero exit status' % syntax) + try: + if os.path.isfile(tmp): + lines = [s.rstrip() for s in open(tmp)] + else: + lines = [] + except Exception: + raise EAsciiDoc,'%s: temp file read error' % syntax + result = separator.join(lines) + finally: + if os.path.isfile(tmp): + os.remove(tmp) + elif name in ('counter','counter2'): + mo = re.match(r'^(?P<attr>[^:]*?)(:(?P<seed>.*))?$', args) + attr = mo.group('attr') + seed = mo.group('seed') + if seed and (not re.match(r'^\d+$', seed) and len(seed) > 1): + message.warning('%s: illegal counter seed: %s' % (syntax,seed)) + return None + if not is_name(attr): + message.warning('%s: illegal attribute name' % syntax) + return None + value = document.attributes.get(attr) + if value: + if not re.match(r'^\d+$', value) and len(value) > 1: + message.warning('%s: illegal counter value: %s' + % (syntax,value)) + return None + if re.match(r'^\d+$', value): + expr = value + '+1' + else: + expr = 'chr(ord("%s")+1)' % value + try: + result = str(eval(expr)) + except Exception: + message.warning('%s: evaluation error: %s' % (syntax, expr)) + else: + if seed: + result = seed + else: + result = '1' + document.attributes[attr] = result + if attrs is not None: + attrs[attr] = result + if name == 'counter2': + result = '' + elif name in ('set','set2'): + mo = re.match(r'^(?P<attr>[^:]*?)(:(?P<value>.*))?$', args) + attr = mo.group('attr') + value = mo.group('value') + if value is None: + value = '' + if attr.endswith('!'): + attr = attr[:-1] + value = None + if not is_name(attr): + message.warning('%s: illegal attribute name' % syntax) + else: + if attrs is not None: + attrs[attr] = value + if name != 'set2': # set2 only updates local attributes. + document.attributes[attr] = value + if value is None: + result = None + else: + result = '' + elif name == 'include': + if not os.path.exists(args): + message.warning('%s: file does not exist' % syntax) + elif not is_safe_file(args): + message.unsafe(syntax) + else: + result = [s.rstrip() for s in open(args)] + if result: + result = subs_attrs(result) + result = separator.join(result) + result = result.expandtabs(reader.tabsize) + else: + result = '' + elif name == 'include1': + result = separator.join(config.include1[args]) + elif name == 'template': + if not args in config.sections: + message.warning('%s: template does not exist' % syntax) + else: + result = [] + for line in config.sections[args]: + line = subs_attrs(line) + if line is not None: + result.append(line) + result = '\n'.join(result) + else: + assert False + if result and name in ('eval3','sys3'): + macros.passthroughs.append(result) + result = '\x07' + str(len(macros.passthroughs)-1) + '\x07' + return result + +def subs_attrs(lines, dictionary=None): + """Substitute 'lines' of text with attributes from the global + document.attributes dictionary and from 'dictionary' ('dictionary' + entries take precedence). Return a tuple of the substituted lines. 'lines' + containing undefined attributes are deleted. If 'lines' is a string then + return a string. + + - Attribute references are substituted in the following order: simple, + conditional, system. + - Attribute references inside 'dictionary' entry values are substituted. + """ + + def end_brace(text,start): + """Return index following end brace that matches brace at start in + text.""" + assert text[start] == '{' + n = 0 + result = start + for c in text[start:]: + # Skip braces that are followed by a backslash. + if result == len(text)-1 or text[result+1] != '\\': + if c == '{': n = n + 1 + elif c == '}': n = n - 1 + result = result + 1 + if n == 0: break + return result + + if type(lines) == str: + string_result = True + lines = [lines] + else: + string_result = False + if dictionary is None: + attrs = document.attributes + else: + # Remove numbered document attributes so they don't clash with + # attribute list positional attributes. + attrs = {} + for k,v in document.attributes.items(): + if not re.match(r'^\d+$', k): + attrs[k] = v + # Substitute attribute references inside dictionary values. + for k,v in dictionary.items(): + if v is None: + del dictionary[k] + else: + v = subs_attrs(str(v)) + if v is None: + del dictionary[k] + else: + dictionary[k] = v + attrs.update(dictionary) + # Substitute all attributes in all lines. + result = [] + for line in lines: + # Make it easier for regular expressions. + line = line.replace('\\{','{\\') + line = line.replace('\\}','}\\') + # Expand simple attributes ({name}). + # Nested attributes not allowed. + reo = re.compile(r'(?su)\{(?P<name>[^\\\W][-\w]*?)\}(?!\\)') + pos = 0 + while True: + mo = reo.search(line,pos) + if not mo: break + s = attrs.get(mo.group('name')) + if s is None: + pos = mo.end() + else: + s = str(s) + line = line[:mo.start()] + s + line[mo.end():] + pos = mo.start() + len(s) + # Expand conditional attributes. + # Single name -- higher precedence. + reo1 = re.compile(r'(?su)\{(?P<name>[^\\\W][-\w]*?)' \ + r'(?P<op>\=|\?|!|#|%|@|\$)' \ + r'(?P<value>.*?)\}(?!\\)') + # Multiple names (n1,n2,... or n1+n2+...) -- lower precedence. + reo2 = re.compile(r'(?su)\{(?P<name>[^\\\W][-\w'+OR+AND+r']*?)' \ + r'(?P<op>\=|\?|!|#|%|@|\$)' \ + r'(?P<value>.*?)\}(?!\\)') + for reo in [reo1,reo2]: + pos = 0 + while True: + mo = reo.search(line,pos) + if not mo: break + attr = mo.group() + name = mo.group('name') + if reo == reo2: + if OR in name: + sep = OR + else: + sep = AND + names = [s.strip() for s in name.split(sep) if s.strip() ] + for n in names: + if not re.match(r'^[^\\\W][-\w]*$',n): + message.error('illegal attribute syntax: %s' % attr) + if sep == OR: + # Process OR name expression: n1,n2,... + for n in names: + if attrs.get(n) is not None: + lval = '' + break + else: + lval = None + else: + # Process AND name expression: n1+n2+... + for n in names: + if attrs.get(n) is None: + lval = None + break + else: + lval = '' + else: + lval = attrs.get(name) + op = mo.group('op') + # mo.end() not good enough because '{x={y}}' matches '{x={y}'. + end = end_brace(line,mo.start()) + rval = line[mo.start('value'):end-1] + UNDEFINED = '{zzzzz}' + if lval is None: + if op == '=': s = rval + elif op == '?': s = '' + elif op == '!': s = rval + elif op == '#': s = UNDEFINED # So the line is dropped. + elif op == '%': s = rval + elif op in ('@','$'): + s = UNDEFINED # So the line is dropped. + else: + assert False, 'illegal attribute: %s' % attr + else: + if op == '=': s = lval + elif op == '?': s = rval + elif op == '!': s = '' + elif op == '#': s = rval + elif op == '%': s = UNDEFINED # So the line is dropped. + elif op in ('@','$'): + v = re.split(r'(?<!\\):',rval) + if len(v) not in (2,3): + message.error('illegal attribute syntax: %s' % attr) + s = '' + elif not is_re('^'+v[0]+'$'): + message.error('illegal attribute regexp: %s' % attr) + s = '' + else: + v = [s.replace('\\:',':') for s in v] + re_mo = re.match('^'+v[0]+'$',lval) + if op == '@': + if re_mo: + s = v[1] # {<name>@<re>:<v1>[:<v2>]} + else: + if len(v) == 3: # {<name>@<re>:<v1>:<v2>} + s = v[2] + else: # {<name>@<re>:<v1>} + s = '' + else: + if re_mo: + if len(v) == 2: # {<name>$<re>:<v1>} + s = v[1] + elif v[1] == '': # {<name>$<re>::<v2>} + s = UNDEFINED # So the line is dropped. + else: # {<name>$<re>:<v1>:<v2>} + s = v[1] + else: + if len(v) == 2: # {<name>$<re>:<v1>} + s = UNDEFINED # So the line is dropped. + else: # {<name>$<re>:<v1>:<v2>} + s = v[2] + else: + assert False, 'illegal attribute: %s' % attr + s = str(s) + line = line[:mo.start()] + s + line[end:] + pos = mo.start() + len(s) + # Drop line if it contains unsubstituted {name} references. + skipped = re.search(r'(?su)\{[^\\\W][-\w]*?\}(?!\\)', line) + if skipped: + trace('dropped line', line) + continue; + # Expand system attributes (eval has precedence). + reos = [ + re.compile(r'(?su)\{(?P<action>eval):(?P<expr>.*?)\}(?!\\)'), + re.compile(r'(?su)\{(?P<action>[^\\\W][-\w]*?):(?P<expr>.*?)\}(?!\\)'), + ] + skipped = False + for reo in reos: + pos = 0 + while True: + mo = reo.search(line,pos) + if not mo: break + expr = mo.group('expr') + action = mo.group('action') + expr = expr.replace('{\\','{') + expr = expr.replace('}\\','}') + s = system(action, expr, attrs=dictionary) + if dictionary is not None and action in ('counter','counter2','set','set2'): + # These actions create and update attributes. + attrs.update(dictionary) + if s is None: + # Drop line if the action returns None. + skipped = True + break + line = line[:mo.start()] + s + line[mo.end():] + pos = mo.start() + len(s) + if skipped: + break + if not skipped: + # Remove backslash from escaped entries. + line = line.replace('{\\','{') + line = line.replace('}\\','}') + result.append(line) + if string_result: + if result: + return '\n'.join(result) + else: + return None + else: + return tuple(result) + +def char_encoding(): + encoding = document.attributes.get('encoding') + if encoding: + try: + codecs.lookup(encoding) + except LookupError,e: + raise EAsciiDoc,str(e) + return encoding + +def char_len(s): + return len(char_decode(s)) + +east_asian_widths = {'W': 2, # Wide + 'F': 2, # Full-width (wide) + 'Na': 1, # Narrow + 'H': 1, # Half-width (narrow) + 'N': 1, # Neutral (not East Asian, treated as narrow) + 'A': 1} # Ambiguous (s/b wide in East Asian context, + # narrow otherwise, but that doesn't work) +"""Mapping of result codes from `unicodedata.east_asian_width()` to character +column widths.""" + +def column_width(s): + text = char_decode(s) + if isinstance(text, unicode): + width = 0 + for c in text: + width += east_asian_widths[unicodedata.east_asian_width(c)] + return width + else: + return len(text) + +def char_decode(s): + if char_encoding(): + try: + return s.decode(char_encoding()) + except Exception: + raise EAsciiDoc, \ + "'%s' codec can't decode \"%s\"" % (char_encoding(), s) + else: + return s + +def char_encode(s): + if char_encoding(): + return s.encode(char_encoding()) + else: + return s + +def time_str(t): + """Convert seconds since the Epoch to formatted local time string.""" + t = time.localtime(t) + s = time.strftime('%H:%M:%S',t) + if time.daylight and t.tm_isdst == 1: + result = s + ' ' + time.tzname[1] + else: + result = s + ' ' + time.tzname[0] + # Attempt to convert the localtime to the output encoding. + try: + result = char_encode(result.decode(locale.getdefaultlocale()[1])) + except Exception: + pass + return result + +def date_str(t): + """Convert seconds since the Epoch to formatted local date string.""" + t = time.localtime(t) + return time.strftime('%Y-%m-%d',t) + + +class Lex: + """Lexical analysis routines. Static methods and attributes only.""" + prev_element = None + prev_cursor = None + def __init__(self): + raise AssertionError,'no class instances allowed' + @staticmethod + def next(): + """Returns class of next element on the input (None if EOF). The + reader is assumed to be at the first line following a previous element, + end of file or line one. Exits with the reader pointing to the first + line of the next element or EOF (leading blank lines are skipped).""" + reader.skip_blank_lines() + if reader.eof(): return None + # Optimization: If we've already checked for an element at this + # position return the element. + if Lex.prev_element and Lex.prev_cursor == reader.cursor: + return Lex.prev_element + if AttributeEntry.isnext(): + result = AttributeEntry + elif AttributeList.isnext(): + result = AttributeList + elif BlockTitle.isnext() and not tables_OLD.isnext(): + result = BlockTitle + elif Title.isnext(): + if AttributeList.style() == 'float': + result = FloatingTitle + else: + result = Title + elif macros.isnext(): + result = macros.current + elif lists.isnext(): + result = lists.current + elif blocks.isnext(): + result = blocks.current + elif tables_OLD.isnext(): + result = tables_OLD.current + elif tables.isnext(): + result = tables.current + else: + if not paragraphs.isnext(): + raise EAsciiDoc,'paragraph expected' + result = paragraphs.current + # Optimization: Cache answer. + Lex.prev_cursor = reader.cursor + Lex.prev_element = result + return result + + @staticmethod + def canonical_subs(options): + """Translate composite subs values.""" + if len(options) == 1: + if options[0] == 'none': + options = () + elif options[0] == 'normal': + options = config.subsnormal + elif options[0] == 'verbatim': + options = config.subsverbatim + return options + + @staticmethod + def subs_1(s,options): + """Perform substitution specified in 'options' (in 'options' order).""" + if not s: + return s + if document.attributes.get('plaintext') is not None: + options = ('specialcharacters',) + result = s + options = Lex.canonical_subs(options) + for o in options: + if o == 'specialcharacters': + result = config.subs_specialchars(result) + elif o == 'attributes': + result = subs_attrs(result) + elif o == 'quotes': + result = subs_quotes(result) + elif o == 'specialwords': + result = config.subs_specialwords(result) + elif o in ('replacements','replacements2'): + result = config.subs_replacements(result,o) + elif o == 'macros': + result = macros.subs(result) + elif o == 'callouts': + result = macros.subs(result,callouts=True) + else: + raise EAsciiDoc,'illegal substitution option: %s' % o + trace(o, s, result) + if not result: + break + return result + + @staticmethod + def subs(lines,options): + """Perform inline processing specified by 'options' (in 'options' + order) on sequence of 'lines'.""" + if not lines or not options: + return lines + options = Lex.canonical_subs(options) + # Join lines so quoting can span multiple lines. + para = '\n'.join(lines) + if 'macros' in options: + para = macros.extract_passthroughs(para) + for o in options: + if o == 'attributes': + # If we don't substitute attributes line-by-line then a single + # undefined attribute will drop the entire paragraph. + lines = subs_attrs(para.split('\n')) + para = '\n'.join(lines) + else: + para = Lex.subs_1(para,(o,)) + if 'macros' in options: + para = macros.restore_passthroughs(para) + return para.splitlines() + + @staticmethod + def set_margin(lines, margin=0): + """Utility routine that sets the left margin to 'margin' space in a + block of non-blank lines.""" + # Calculate width of block margin. + lines = list(lines) + width = len(lines[0]) + for s in lines: + i = re.search(r'\S',s).start() + if i < width: width = i + # Strip margin width from all lines. + for i in range(len(lines)): + lines[i] = ' '*margin + lines[i][width:] + return lines + +#--------------------------------------------------------------------------- +# Document element classes parse AsciiDoc reader input and write DocBook writer +# output. +#--------------------------------------------------------------------------- +class Document(object): + + # doctype property. + def getdoctype(self): + return self.attributes.get('doctype') + def setdoctype(self,doctype): + self.attributes['doctype'] = doctype + doctype = property(getdoctype,setdoctype) + + # backend property. + def getbackend(self): + return self.attributes.get('backend') + def setbackend(self,backend): + if backend: + backend = self.attributes.get('backend-alias-' + backend, backend) + self.attributes['backend'] = backend + backend = property(getbackend,setbackend) + + def __init__(self): + self.infile = None # Source file name. + self.outfile = None # Output file name. + self.attributes = InsensitiveDict() + self.level = 0 # 0 => front matter. 1,2,3 => sect1,2,3. + self.has_errors = False # Set true if processing errors were flagged. + self.has_warnings = False # Set true if warnings were flagged. + self.safe = False # Default safe mode. + def update_attributes(self,attrs=None): + """ + Set implicit attributes and attributes in 'attrs'. + """ + t = time.time() + self.attributes['localtime'] = time_str(t) + self.attributes['localdate'] = date_str(t) + self.attributes['asciidoc-version'] = VERSION + self.attributes['asciidoc-file'] = APP_FILE + self.attributes['asciidoc-dir'] = APP_DIR + self.attributes['asciidoc-confdir'] = CONF_DIR + self.attributes['user-dir'] = USER_DIR + if config.verbose: + self.attributes['verbose'] = '' + # Update with configuration file attributes. + if attrs: + self.attributes.update(attrs) + # Update with command-line attributes. + self.attributes.update(config.cmd_attrs) + # Extract miscellaneous configuration section entries from attributes. + if attrs: + config.load_miscellaneous(attrs) + config.load_miscellaneous(config.cmd_attrs) + self.attributes['newline'] = config.newline + # File name related attributes can't be overridden. + if self.infile is not None: + if self.infile and os.path.exists(self.infile): + t = os.path.getmtime(self.infile) + elif self.infile == '<stdin>': + t = time.time() + else: + t = None + if t: + self.attributes['doctime'] = time_str(t) + self.attributes['docdate'] = date_str(t) + if self.infile != '<stdin>': + self.attributes['infile'] = self.infile + self.attributes['indir'] = os.path.dirname(self.infile) + self.attributes['docfile'] = self.infile + self.attributes['docdir'] = os.path.dirname(self.infile) + self.attributes['docname'] = os.path.splitext( + os.path.basename(self.infile))[0] + if self.outfile: + if self.outfile != '<stdout>': + self.attributes['outfile'] = self.outfile + self.attributes['outdir'] = os.path.dirname(self.outfile) + if self.infile == '<stdin>': + self.attributes['docname'] = os.path.splitext( + os.path.basename(self.outfile))[0] + ext = os.path.splitext(self.outfile)[1][1:] + elif config.outfilesuffix: + ext = config.outfilesuffix[1:] + else: + ext = '' + if ext: + self.attributes['filetype'] = ext + self.attributes['filetype-'+ext] = '' + def load_lang(self): + """ + Load language configuration file. + """ + lang = self.attributes.get('lang') + if lang is None: + filename = 'lang-en.conf' # Default language file. + else: + filename = 'lang-' + lang + '.conf' + if config.load_from_dirs(filename): + self.attributes['lang'] = lang # Reinstate new lang attribute. + else: + if lang is None: + # The default language file must exist. + message.error('missing conf file: %s' % filename, halt=True) + else: + message.warning('missing language conf file: %s' % filename) + def set_deprecated_attribute(self,old,new): + """ + Ensures the 'old' name of an attribute that was renamed to 'new' is + still honored. + """ + if self.attributes.get(new) is None: + if self.attributes.get(old) is not None: + self.attributes[new] = self.attributes[old] + else: + self.attributes[old] = self.attributes[new] + def consume_attributes_and_comments(self,comments_only=False,noblanks=False): + """ + Returns True if one or more attributes or comments were consumed. + If 'noblanks' is True then consumation halts if a blank line is + encountered. + """ + result = False + finished = False + while not finished: + finished = True + if noblanks and not reader.read_next(): return result + if blocks.isnext() and 'skip' in blocks.current.options: + result = True + finished = False + blocks.current.translate() + if noblanks and not reader.read_next(): return result + if macros.isnext() and macros.current.name == 'comment': + result = True + finished = False + macros.current.translate() + if not comments_only: + if AttributeEntry.isnext(): + result = True + finished = False + AttributeEntry.translate() + if AttributeList.isnext(): + result = True + finished = False + AttributeList.translate() + return result + def parse_header(self,doctype,backend): + """ + Parses header, sets corresponding document attributes and finalizes + document doctype and backend properties. + Returns False if the document does not have a header. + 'doctype' and 'backend' are the doctype and backend option values + passed on the command-line, None if no command-line option was not + specified. + """ + assert self.level == 0 + # Skip comments and attribute entries that preceed the header. + self.consume_attributes_and_comments() + if doctype is not None: + # Command-line overrides header. + self.doctype = doctype + elif self.doctype is None: + # Was not set on command-line or in document header. + self.doctype = DEFAULT_DOCTYPE + # Process document header. + has_header = (Title.isnext() and Title.level == 0 + and AttributeList.style() != 'float') + if self.doctype == 'manpage' and not has_header: + message.error('manpage document title is mandatory',halt=True) + if has_header: + Header.parse() + # Command-line entries override header derived entries. + self.attributes.update(config.cmd_attrs) + # DEPRECATED: revision renamed to revnumber. + self.set_deprecated_attribute('revision','revnumber') + # DEPRECATED: date renamed to revdate. + self.set_deprecated_attribute('date','revdate') + if doctype is not None: + # Command-line overrides header. + self.doctype = doctype + if backend is not None: + # Command-line overrides header. + self.backend = backend + elif self.backend is None: + # Was not set on command-line or in document header. + self.backend = DEFAULT_BACKEND + else: + # Has been set in document header. + self.backend = self.backend # Translate alias in header. + assert self.doctype in ('article','manpage','book'), 'illegal document type' + return has_header + def translate(self,has_header): + if self.doctype == 'manpage': + # Translate mandatory NAME section. + if Lex.next() is not Title: + message.error('name section expected') + else: + Title.translate() + if Title.level != 1: + message.error('name section title must be at level 1') + if not isinstance(Lex.next(),Paragraph): + message.error('malformed name section body') + lines = reader.read_until(r'^$') + s = ' '.join(lines) + mo = re.match(r'^(?P<manname>.*?)\s+-\s+(?P<manpurpose>.*)$',s) + if not mo: + message.error('malformed name section body') + self.attributes['manname'] = mo.group('manname').strip() + self.attributes['manpurpose'] = mo.group('manpurpose').strip() + names = [s.strip() for s in self.attributes['manname'].split(',')] + if len(names) > 9: + message.warning('to many manpage names') + for i,name in enumerate(names): + self.attributes['manname%d' % (i+1)] = name + if has_header: + # Do postponed substitutions (backend confs have been loaded). + self.attributes['doctitle'] = Title.dosubs(self.attributes['doctitle']) + if config.header_footer: + hdr = config.subs_section('header',{}) + writer.write(hdr,trace='header') + if 'title' in self.attributes: + del self.attributes['title'] + self.consume_attributes_and_comments() + if self.doctype in ('article','book'): + # Translate 'preamble' (untitled elements between header + # and first section title). + if Lex.next() is not Title: + stag,etag = config.section2tags('preamble') + writer.write(stag,trace='preamble open') + Section.translate_body() + writer.write(etag,trace='preamble close') + elif self.doctype == 'manpage' and 'name' in config.sections: + writer.write(config.subs_section('name',{}), trace='name') + else: + self.process_author_names() + if config.header_footer: + hdr = config.subs_section('header',{}) + writer.write(hdr,trace='header') + if Lex.next() is not Title: + Section.translate_body() + # Process remaining sections. + while not reader.eof(): + if Lex.next() is not Title: + raise EAsciiDoc,'section title expected' + Section.translate() + Section.setlevel(0) # Write remaining unwritten section close tags. + # Substitute document parameters and write document footer. + if config.header_footer: + ftr = config.subs_section('footer',{}) + writer.write(ftr,trace='footer') + def parse_author(self,s): + """ Return False if the author is malformed.""" + attrs = self.attributes # Alias for readability. + s = s.strip() + mo = re.match(r'^(?P<name1>[^<>\s]+)' + '(\s+(?P<name2>[^<>\s]+))?' + '(\s+(?P<name3>[^<>\s]+))?' + '(\s+<(?P<email>\S+)>)?$',s) + if not mo: + # Names that don't match the formal specification. + if s: + attrs['firstname'] = s + return + firstname = mo.group('name1') + if mo.group('name3'): + middlename = mo.group('name2') + lastname = mo.group('name3') + else: + middlename = None + lastname = mo.group('name2') + firstname = firstname.replace('_',' ') + if middlename: + middlename = middlename.replace('_',' ') + if lastname: + lastname = lastname.replace('_',' ') + email = mo.group('email') + if firstname: + attrs['firstname'] = firstname + if middlename: + attrs['middlename'] = middlename + if lastname: + attrs['lastname'] = lastname + if email: + attrs['email'] = email + return + def process_author_names(self): + """ Calculate any missing author related attributes.""" + attrs = self.attributes # Alias for readability. + firstname = attrs.get('firstname','') + middlename = attrs.get('middlename','') + lastname = attrs.get('lastname','') + author = attrs.get('author') + initials = attrs.get('authorinitials') + if author and not (firstname or middlename or lastname): + self.parse_author(author) + attrs['author'] = author.replace('_',' ') + self.process_author_names() + return + if not author: + author = '%s %s %s' % (firstname, middlename, lastname) + author = author.strip() + author = re.sub(r'\s+',' ', author) + if not initials: + initials = (char_decode(firstname)[:1] + + char_decode(middlename)[:1] + char_decode(lastname)[:1]) + initials = char_encode(initials).upper() + names = [firstname,middlename,lastname,author,initials] + for i,v in enumerate(names): + v = config.subs_specialchars(v) + v = subs_attrs(v) + names[i] = v + firstname,middlename,lastname,author,initials = names + if firstname: + attrs['firstname'] = firstname + if middlename: + attrs['middlename'] = middlename + if lastname: + attrs['lastname'] = lastname + if author: + attrs['author'] = author + if initials: + attrs['authorinitials'] = initials + if author: + attrs['authored'] = '' + + +class Header: + """Static methods and attributes only.""" + REV_LINE_RE = r'^(\D*(?P<revnumber>.*?),)?(?P<revdate>.*?)(:\s*(?P<revremark>.*))?$' + RCS_ID_RE = r'^\$Id: \S+ (?P<revnumber>\S+) (?P<revdate>\S+) \S+ (?P<author>\S+) (\S+ )?\$$' + def __init__(self): + raise AssertionError,'no class instances allowed' + @staticmethod + def parse(): + assert Lex.next() is Title and Title.level == 0 + attrs = document.attributes # Alias for readability. + # Postpone title subs until backend conf files have been loaded. + Title.translate(skipsubs=True) + attrs['doctitle'] = Title.attributes['title'] + document.consume_attributes_and_comments(noblanks=True) + s = reader.read_next() + mo = None + if s: + # Process first header line after the title that is not a comment + # or an attribute entry. + s = reader.read() + mo = re.match(Header.RCS_ID_RE,s) + if not mo: + document.parse_author(s) + document.consume_attributes_and_comments(noblanks=True) + if reader.read_next(): + # Process second header line after the title that is not a + # comment or an attribute entry. + s = reader.read() + s = subs_attrs(s) + if s: + mo = re.match(Header.RCS_ID_RE,s) + if not mo: + mo = re.match(Header.REV_LINE_RE,s) + document.consume_attributes_and_comments(noblanks=True) + s = attrs.get('revnumber') + if s: + mo = re.match(Header.RCS_ID_RE,s) + if mo: + revnumber = mo.group('revnumber') + if revnumber: + attrs['revnumber'] = revnumber.strip() + author = mo.groupdict().get('author') + if author and 'firstname' not in attrs: + document.parse_author(author) + revremark = mo.groupdict().get('revremark') + if revremark is not None: + revremark = [revremark] + # Revision remarks can continue on following lines. + while reader.read_next(): + if document.consume_attributes_and_comments(noblanks=True): + break + revremark.append(reader.read()) + revremark = Lex.subs(revremark,['normal']) + revremark = '\n'.join(revremark).strip() + attrs['revremark'] = revremark + revdate = mo.group('revdate') + if revdate: + attrs['revdate'] = revdate.strip() + elif revnumber or revremark: + # Set revision date to ensure valid DocBook revision. + attrs['revdate'] = attrs['docdate'] + document.process_author_names() + if document.doctype == 'manpage': + # manpage title formatted like mantitle(manvolnum). + mo = re.match(r'^(?P<mantitle>.*)\((?P<manvolnum>.*)\)$', + attrs['doctitle']) + if not mo: + message.error('malformed manpage title') + else: + mantitle = mo.group('mantitle').strip() + mantitle = subs_attrs(mantitle) + if mantitle is None: + message.error('undefined attribute in manpage title') + # mantitle is lowered only if in ALL CAPS + if mantitle == mantitle.upper(): + mantitle = mantitle.lower() + attrs['mantitle'] = mantitle; + attrs['manvolnum'] = mo.group('manvolnum').strip() + +class AttributeEntry: + """Static methods and attributes only.""" + pattern = None + subs = None + name = None + name2 = None + value = None + attributes = {} # Accumulates all the parsed attribute entries. + def __init__(self): + raise AssertionError,'no class instances allowed' + @staticmethod + def isnext(): + result = False # Assume not next. + if not AttributeEntry.pattern: + pat = document.attributes.get('attributeentry-pattern') + if not pat: + message.error("[attributes] missing 'attributeentry-pattern' entry") + AttributeEntry.pattern = pat + line = reader.read_next() + if line: + # Attribute entry formatted like :<name>[.<name2>]:[ <value>] + mo = re.match(AttributeEntry.pattern,line) + if mo: + AttributeEntry.name = mo.group('attrname') + AttributeEntry.name2 = mo.group('attrname2') + AttributeEntry.value = mo.group('attrvalue') or '' + AttributeEntry.value = AttributeEntry.value.strip() + result = True + return result + @staticmethod + def translate(): + assert Lex.next() is AttributeEntry + attr = AttributeEntry # Alias for brevity. + reader.read() # Discard attribute entry from reader. + while attr.value.endswith(' +'): + if not reader.read_next(): break + attr.value = attr.value[:-1] + reader.read().strip() + if attr.name2 is not None: + # Configuration file attribute. + if attr.name2 != '': + # Section entry attribute. + section = {} + # Some sections can have name! syntax. + if attr.name in ('attributes','miscellaneous') and attr.name2[-1] == '!': + section[attr.name] = [attr.name2] + else: + section[attr.name] = ['%s=%s' % (attr.name2,attr.value)] + config.load_sections(section) + config.load_miscellaneous(config.conf_attrs) + else: + # Markup template section attribute. + if attr.name in config.sections: + config.sections[attr.name] = [attr.value] + else: + message.warning('missing configuration section: %s' % attr.name) + else: + # Normal attribute. + if attr.name[-1] == '!': + # Names like name! undefine the attribute. + attr.name = attr.name[:-1] + attr.value = None + # Strip white space and illegal name chars. + attr.name = re.sub(r'(?u)[^\w\-_]', '', attr.name).lower() + # Don't override most command-line attributes. + if attr.name in config.cmd_attrs \ + and attr.name not in ('trace','numbered'): + return + # Update document attributes with attribute value. + if attr.value is not None: + mo = re.match(r'^pass:(?P<attrs>.*)\[(?P<value>.*)\]$', attr.value) + if mo: + # Inline passthrough syntax. + attr.subs = mo.group('attrs') + attr.value = mo.group('value') # Passthrough. + else: + # Default substitution. + # DEPRECATED: attributeentry-subs + attr.subs = document.attributes.get('attributeentry-subs', + 'specialcharacters,attributes') + attr.subs = parse_options(attr.subs, SUBS_OPTIONS, + 'illegal substitution option') + attr.value = Lex.subs((attr.value,), attr.subs) + attr.value = writer.newline.join(attr.value) + document.attributes[attr.name] = attr.value + elif attr.name in document.attributes: + del document.attributes[attr.name] + attr.attributes[attr.name] = attr.value + +class AttributeList: + """Static methods and attributes only.""" + pattern = None + match = None + attrs = {} + def __init__(self): + raise AssertionError,'no class instances allowed' + @staticmethod + def initialize(): + if not 'attributelist-pattern' in document.attributes: + message.error("[attributes] missing 'attributelist-pattern' entry") + AttributeList.pattern = document.attributes['attributelist-pattern'] + @staticmethod + def isnext(): + result = False # Assume not next. + line = reader.read_next() + if line: + mo = re.match(AttributeList.pattern, line) + if mo: + AttributeList.match = mo + result = True + return result + @staticmethod + def translate(): + assert Lex.next() is AttributeList + reader.read() # Discard attribute list from reader. + attrs = {} + d = AttributeList.match.groupdict() + for k,v in d.items(): + if v is not None: + if k == 'attrlist': + v = subs_attrs(v) + if v: + parse_attributes(v, attrs) + else: + AttributeList.attrs[k] = v + AttributeList.subs(attrs) + AttributeList.attrs.update(attrs) + @staticmethod + def subs(attrs): + '''Substitute single quoted attribute values normally.''' + reo = re.compile(r"^'.*'$") + for k,v in attrs.items(): + if reo.match(str(v)): + attrs[k] = Lex.subs_1(v[1:-1],SUBS_NORMAL) + @staticmethod + def style(): + return AttributeList.attrs.get('style') or AttributeList.attrs.get('1') + @staticmethod + def consume(d): + """Add attribute list to the dictionary 'd' and reset the + list.""" + if AttributeList.attrs: + d.update(AttributeList.attrs) + AttributeList.attrs = {} + # Generate option attributes. + if 'options' in d: + options = parse_options(d['options'], (), 'illegal option name') + for option in options: + d[option+'-option'] = '' + +class BlockTitle: + """Static methods and attributes only.""" + title = None + pattern = None + def __init__(self): + raise AssertionError,'no class instances allowed' + @staticmethod + def isnext(): + result = False # Assume not next. + line = reader.read_next() + if line: + mo = re.match(BlockTitle.pattern,line) + if mo: + BlockTitle.title = mo.group('title') + result = True + return result + @staticmethod + def translate(): + assert Lex.next() is BlockTitle + reader.read() # Discard title from reader. + # Perform title substitutions. + if not Title.subs: + Title.subs = config.subsnormal + s = Lex.subs((BlockTitle.title,), Title.subs) + s = writer.newline.join(s) + if not s: + message.warning('blank block title') + BlockTitle.title = s + @staticmethod + def consume(d): + """If there is a title add it to dictionary 'd' then reset title.""" + if BlockTitle.title: + d['title'] = BlockTitle.title + BlockTitle.title = None + +class Title: + """Processes Header and Section titles. Static methods and attributes + only.""" + # Class variables + underlines = ('==','--','~~','^^','++') # Levels 0,1,2,3,4. + subs = () + pattern = None + level = 0 + attributes = {} + sectname = None + section_numbers = [0]*len(underlines) + dump_dict = {} + linecount = None # Number of lines in title (1 or 2). + def __init__(self): + raise AssertionError,'no class instances allowed' + @staticmethod + def translate(skipsubs=False): + """Parse the Title.attributes and Title.level from the reader. The + real work has already been done by parse().""" + assert Lex.next() in (Title,FloatingTitle) + # Discard title from reader. + for i in range(Title.linecount): + reader.read() + Title.setsectname() + if not skipsubs: + Title.attributes['title'] = Title.dosubs(Title.attributes['title']) + @staticmethod + def dosubs(title): + """ + Perform title substitutions. + """ + if not Title.subs: + Title.subs = config.subsnormal + title = Lex.subs((title,), Title.subs) + title = writer.newline.join(title) + if not title: + message.warning('blank section title') + return title + @staticmethod + def isnext(): + lines = reader.read_ahead(2) + return Title.parse(lines) + @staticmethod + def parse(lines): + """Parse title at start of lines tuple.""" + if len(lines) == 0: return False + if len(lines[0]) == 0: return False # Title can't be blank. + # Check for single-line titles. + result = False + for level in range(len(Title.underlines)): + k = 'sect%s' % level + if k in Title.dump_dict: + mo = re.match(Title.dump_dict[k], lines[0]) + if mo: + Title.attributes = mo.groupdict() + Title.level = level + Title.linecount = 1 + result = True + break + if not result: + # Check for double-line titles. + if not Title.pattern: return False # Single-line titles only. + if len(lines) < 2: return False + title,ul = lines[:2] + title_len = column_width(title) + ul_len = char_len(ul) + if ul_len < 2: return False + # Fast elimination check. + if ul[:2] not in Title.underlines: return False + # Length of underline must be within +-3 of title. + if not ((ul_len-3 < title_len < ul_len+3) + # Next test for backward compatibility. + or (ul_len-3 < char_len(title) < ul_len+3)): + return False + # Check for valid repetition of underline character pairs. + s = ul[:2]*((ul_len+1)/2) + if ul != s[:ul_len]: return False + # Don't be fooled by back-to-back delimited blocks, require at + # least one alphanumeric character in title. + if not re.search(r'(?u)\w',title): return False + mo = re.match(Title.pattern, title) + if mo: + Title.attributes = mo.groupdict() + Title.level = list(Title.underlines).index(ul[:2]) + Title.linecount = 2 + result = True + # Check for expected pattern match groups. + if result: + if not 'title' in Title.attributes: + message.warning('[titles] entry has no <title> group') + Title.attributes['title'] = lines[0] + for k,v in Title.attributes.items(): + if v is None: del Title.attributes[k] + try: + Title.level += int(document.attributes.get('leveloffset','0')) + except: + pass + Title.attributes['level'] = str(Title.level) + return result + @staticmethod + def load(entries): + """Load and validate [titles] section entries dictionary.""" + if 'underlines' in entries: + errmsg = 'malformed [titles] underlines entry' + try: + underlines = parse_list(entries['underlines']) + except Exception: + raise EAsciiDoc,errmsg + if len(underlines) != len(Title.underlines): + raise EAsciiDoc,errmsg + for s in underlines: + if len(s) !=2: + raise EAsciiDoc,errmsg + Title.underlines = tuple(underlines) + Title.dump_dict['underlines'] = entries['underlines'] + if 'subs' in entries: + Title.subs = parse_options(entries['subs'], SUBS_OPTIONS, + 'illegal [titles] subs entry') + Title.dump_dict['subs'] = entries['subs'] + if 'sectiontitle' in entries: + pat = entries['sectiontitle'] + if not pat or not is_re(pat): + raise EAsciiDoc,'malformed [titles] sectiontitle entry' + Title.pattern = pat + Title.dump_dict['sectiontitle'] = pat + if 'blocktitle' in entries: + pat = entries['blocktitle'] + if not pat or not is_re(pat): + raise EAsciiDoc,'malformed [titles] blocktitle entry' + BlockTitle.pattern = pat + Title.dump_dict['blocktitle'] = pat + # Load single-line title patterns. + for k in ('sect0','sect1','sect2','sect3','sect4'): + if k in entries: + pat = entries[k] + if not pat or not is_re(pat): + raise EAsciiDoc,'malformed [titles] %s entry' % k + Title.dump_dict[k] = pat + # TODO: Check we have either a Title.pattern or at least one + # single-line title pattern -- can this be done here or do we need + # check routine like the other block checkers? + @staticmethod + def dump(): + dump_section('titles',Title.dump_dict) + @staticmethod + def setsectname(): + """ + Set Title section name: + If the first positional or 'template' attribute is set use it, + next search for section title in [specialsections], + if not found use default 'sect<level>' name. + """ + sectname = AttributeList.attrs.get('1') + if sectname and sectname != 'float': + Title.sectname = sectname + elif 'template' in AttributeList.attrs: + Title.sectname = AttributeList.attrs['template'] + else: + for pat,sect in config.specialsections.items(): + mo = re.match(pat,Title.attributes['title']) + if mo: + title = mo.groupdict().get('title') + if title is not None: + Title.attributes['title'] = title.strip() + else: + Title.attributes['title'] = mo.group().strip() + Title.sectname = sect + break + else: + Title.sectname = 'sect%d' % Title.level + @staticmethod + def getnumber(level): + """Return next section number at section 'level' formatted like + 1.2.3.4.""" + number = '' + for l in range(len(Title.section_numbers)): + n = Title.section_numbers[l] + if l == 0: + continue + elif l < level: + number = '%s%d.' % (number, n) + elif l == level: + number = '%s%d.' % (number, n + 1) + Title.section_numbers[l] = n + 1 + elif l > level: + # Reset unprocessed section levels. + Title.section_numbers[l] = 0 + return number + + +class FloatingTitle(Title): + '''Floated titles are translated differently.''' + @staticmethod + def isnext(): + return Title.isnext() and AttributeList.style() == 'float' + @staticmethod + def translate(): + assert Lex.next() is FloatingTitle + Title.translate() + Section.set_id() + AttributeList.consume(Title.attributes) + template = 'floatingtitle' + if template in config.sections: + stag,etag = config.section2tags(template,Title.attributes) + writer.write(stag,trace='floating title') + else: + message.warning('missing template section: [%s]' % template) + + +class Section: + """Static methods and attributes only.""" + endtags = [] # Stack of currently open section (level,endtag) tuples. + ids = [] # List of already used ids. + def __init__(self): + raise AssertionError,'no class instances allowed' + @staticmethod + def savetag(level,etag): + """Save section end.""" + Section.endtags.append((level,etag)) + @staticmethod + def setlevel(level): + """Set document level and write open section close tags up to level.""" + while Section.endtags and Section.endtags[-1][0] >= level: + writer.write(Section.endtags.pop()[1],trace='section close') + document.level = level + @staticmethod + def gen_id(title): + """ + The normalized value of the id attribute is an NCName according to + the 'Namespaces in XML' Recommendation: + NCName ::= NCNameStartChar NCNameChar* + NCNameChar ::= NameChar - ':' + NCNameStartChar ::= Letter | '_' + NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' + """ + # Replace non-alpha numeric characters in title with underscores and + # convert to lower case. + base_ident = char_encode(re.sub(r'(?u)\W+', '_', + char_decode(title)).strip('_').lower()) + # Prefix the ID name with idprefix attribute or underscore if not + # defined. Prefix ensures the ID does not clash with existing IDs. + idprefix = document.attributes.get('idprefix','_') + base_ident = idprefix + base_ident + i = 1 + while True: + if i == 1: + ident = base_ident + else: + ident = '%s_%d' % (base_ident, i) + if ident not in Section.ids: + Section.ids.append(ident) + return ident + else: + ident = base_ident + i += 1 + @staticmethod + def set_id(): + if not document.attributes.get('sectids') is None \ + and 'id' not in AttributeList.attrs: + # Generate ids for sections. + AttributeList.attrs['id'] = Section.gen_id(Title.attributes['title']) + @staticmethod + def translate(): + assert Lex.next() is Title + prev_sectname = Title.sectname + Title.translate() + if Title.level == 0 and document.doctype != 'book': + message.error('only book doctypes can contain level 0 sections') + if Title.level > document.level \ + and 'basebackend-docbook' in document.attributes \ + and prev_sectname in ('colophon','abstract', \ + 'dedication','glossary','bibliography'): + message.error('%s section cannot contain sub-sections' % prev_sectname) + if Title.level > document.level+1: + # Sub-sections of multi-part book level zero Preface and Appendices + # are meant to be out of sequence. + if document.doctype == 'book' \ + and document.level == 0 \ + and Title.level == 2 \ + and prev_sectname in ('preface','appendix'): + pass + else: + message.warning('section title out of sequence: ' + 'expected level %d, got level %d' + % (document.level+1, Title.level)) + Section.set_id() + Section.setlevel(Title.level) + if 'numbered' in document.attributes: + Title.attributes['sectnum'] = Title.getnumber(document.level) + else: + Title.attributes['sectnum'] = '' + AttributeList.consume(Title.attributes) + stag,etag = config.section2tags(Title.sectname,Title.attributes) + Section.savetag(Title.level,etag) + writer.write(stag,trace='section open: level %d: %s' % + (Title.level, Title.attributes['title'])) + Section.translate_body() + @staticmethod + def translate_body(terminator=Title): + isempty = True + next = Lex.next() + while next and next is not terminator: + if isinstance(terminator,DelimitedBlock) and next is Title: + message.error('section title not permitted in delimited block') + next.translate() + next = Lex.next() + isempty = False + # The section is not empty if contains a subsection. + if next and isempty and Title.level > document.level: + isempty = False + # Report empty sections if invalid markup will result. + if isempty: + if document.backend == 'docbook' and Title.sectname != 'index': + message.error('empty section is not valid') + +class AbstractBlock: + def __init__(self): + # Configuration parameter names common to all blocks. + self.CONF_ENTRIES = ('delimiter','options','subs','presubs','postsubs', + 'posattrs','style','.*-style','template','filter') + self.start = None # File reader cursor at start delimiter. + self.name=None # Configuration file section name. + # Configuration parameters. + self.delimiter=None # Regular expression matching block delimiter. + self.delimiter_reo=None # Compiled delimiter. + self.template=None # template section entry. + self.options=() # options entry list. + self.presubs=None # presubs/subs entry list. + self.postsubs=() # postsubs entry list. + self.filter=None # filter entry. + self.posattrs=() # posattrs entry list. + self.style=None # Default style. + self.styles=OrderedDict() # Each entry is a styles dictionary. + # Before a block is processed it's attributes (from it's + # attributes list) are merged with the block configuration parameters + # (by self.merge_attributes()) resulting in the template substitution + # dictionary (self.attributes) and the block's processing parameters + # (self.parameters). + self.attributes={} + # The names of block parameters. + self.PARAM_NAMES=('template','options','presubs','postsubs','filter') + self.parameters=None + # Leading delimiter match object. + self.mo=None + def short_name(self): + """ Return the text following the last dash in the section name.""" + i = self.name.rfind('-') + if i == -1: + return self.name + else: + return self.name[i+1:] + def error(self, msg, cursor=None, halt=False): + message.error('[%s] %s' % (self.name,msg), cursor, halt) + def is_conf_entry(self,param): + """Return True if param matches an allowed configuration file entry + name.""" + for s in self.CONF_ENTRIES: + if re.match('^'+s+'$',param): + return True + return False + def load(self,name,entries): + """Update block definition from section 'entries' dictionary.""" + self.name = name + self.update_parameters(entries, self, all=True) + def update_parameters(self, src, dst=None, all=False): + """ + Parse processing parameters from src dictionary to dst object. + dst defaults to self.parameters. + If all is True then copy src entries that aren't parameter names. + """ + dst = dst or self.parameters + msg = '[%s] malformed entry %%s: %%s' % self.name + def copy(obj,k,v): + if isinstance(obj,dict): + obj[k] = v + else: + setattr(obj,k,v) + for k,v in src.items(): + if not re.match(r'\d+',k) and not is_name(k): + raise EAsciiDoc, msg % (k,v) + if k == 'template': + if not is_name(v): + raise EAsciiDoc, msg % (k,v) + copy(dst,k,v) + elif k == 'filter': + copy(dst,k,v) + elif k == 'options': + if isinstance(v,str): + v = parse_options(v, (), msg % (k,v)) + # Merge with existing options. + v = tuple(set(dst.options).union(set(v))) + copy(dst,k,v) + elif k in ('subs','presubs','postsubs'): + # Subs is an alias for presubs. + if k == 'subs': k = 'presubs' + if isinstance(v,str): + v = parse_options(v, SUBS_OPTIONS, msg % (k,v)) + copy(dst,k,v) + elif k == 'delimiter': + if v and is_re(v): + copy(dst,k,v) + else: + raise EAsciiDoc, msg % (k,v) + elif k == 'style': + if is_name(v): + copy(dst,k,v) + else: + raise EAsciiDoc, msg % (k,v) + elif k == 'posattrs': + v = parse_options(v, (), msg % (k,v)) + copy(dst,k,v) + else: + mo = re.match(r'^(?P<style>.*)-style$',k) + if mo: + if not v: + raise EAsciiDoc, msg % (k,v) + style = mo.group('style') + if not is_name(style): + raise EAsciiDoc, msg % (k,v) + d = {} + if not parse_named_attributes(v,d): + raise EAsciiDoc, msg % (k,v) + if 'subs' in d: + # Subs is an alias for presubs. + d['presubs'] = d['subs'] + del d['subs'] + self.styles[style] = d + elif all or k in self.PARAM_NAMES: + copy(dst,k,v) # Derived class specific entries. + def get_param(self,name,params=None): + """ + Return named processing parameter from params dictionary. + If the parameter is not in params look in self.parameters. + """ + if params and name in params: + return params[name] + elif name in self.parameters: + return self.parameters[name] + else: + return None + def get_subs(self,params=None): + """ + Return (presubs,postsubs) tuple. + """ + presubs = self.get_param('presubs',params) + postsubs = self.get_param('postsubs',params) + return (presubs,postsubs) + def dump(self): + """Write block definition to stdout.""" + write = lambda s: sys.stdout.write('%s%s' % (s,writer.newline)) + write('['+self.name+']') + if self.is_conf_entry('delimiter'): + write('delimiter='+self.delimiter) + if self.template: + write('template='+self.template) + if self.options: + write('options='+','.join(self.options)) + if self.presubs: + if self.postsubs: + write('presubs='+','.join(self.presubs)) + else: + write('subs='+','.join(self.presubs)) + if self.postsubs: + write('postsubs='+','.join(self.postsubs)) + if self.filter: + write('filter='+self.filter) + if self.posattrs: + write('posattrs='+','.join(self.posattrs)) + if self.style: + write('style='+self.style) + if self.styles: + for style,d in self.styles.items(): + s = '' + for k,v in d.items(): s += '%s=%r,' % (k,v) + write('%s-style=%s' % (style,s[:-1])) + def validate(self): + """Validate block after the complete configuration has been loaded.""" + if self.is_conf_entry('delimiter') and not self.delimiter: + raise EAsciiDoc,'[%s] missing delimiter' % self.name + if self.style: + if not is_name(self.style): + raise EAsciiDoc, 'illegal style name: %s' % self.style + if not self.style in self.styles: + if not isinstance(self,List): # Lists don't have templates. + message.warning('[%s] \'%s\' style not in %s' % ( + self.name,self.style,self.styles.keys())) + # Check all styles for missing templates. + all_styles_have_template = True + for k,v in self.styles.items(): + t = v.get('template') + if t and not t in config.sections: + # Defer check if template name contains attributes. + if not re.search(r'{.+}',t): + message.warning('missing template section: [%s]' % t) + if not t: + all_styles_have_template = False + # Check we have a valid template entry or alternatively that all the + # styles have templates. + if self.is_conf_entry('template') and not 'skip' in self.options: + if self.template: + if not self.template in config.sections: + # Defer check if template name contains attributes. + if not re.search(r'{.+}',self.template): + message.warning('missing template section: [%s]' + % self.template) + elif not all_styles_have_template: + if not isinstance(self,List): # Lists don't have templates. + message.warning('missing styles templates: [%s]' % self.name) + def isnext(self): + """Check if this block is next in document reader.""" + result = False + reader.skip_blank_lines() + if reader.read_next(): + if not self.delimiter_reo: + # Cache compiled delimiter optimization. + self.delimiter_reo = re.compile(self.delimiter) + mo = self.delimiter_reo.match(reader.read_next()) + if mo: + self.mo = mo + result = True + return result + def translate(self): + """Translate block from document reader.""" + if not self.presubs: + self.presubs = config.subsnormal + if reader.cursor: + self.start = reader.cursor[:] + def merge_attributes(self,attrs,params=[]): + """ + Use the current blocks attribute list (attrs dictionary) to build a + dictionary of block processing parameters (self.parameters) and tag + substitution attributes (self.attributes). + + 1. Copy the default parameters (self.*) to self.parameters. + self.parameters are used internally to render the current block. + Optional params array of additional parameters. + + 2. Copy attrs to self.attributes. self.attributes are used for template + and tag substitution in the current block. + + 3. If a style attribute was specified update self.parameters with the + corresponding style parameters; if there are any style parameters + remaining add them to self.attributes (existing attribute list entries + take precedence). + + 4. Set named positional attributes in self.attributes if self.posattrs + was specified. + + 5. Finally self.parameters is updated with any corresponding parameters + specified in attrs. + + """ + + def check_array_parameter(param): + # Check the parameter is a sequence type. + if not is_array(self.parameters[param]): + message.error('malformed presubs attribute: %s' % + self.parameters[param]) + # Revert to default value. + self.parameters[param] = getattr(self,param) + + params = list(self.PARAM_NAMES) + params + self.attributes = {} + if self.style: + # If a default style is defined make it available in the template. + self.attributes['style'] = self.style + self.attributes.update(attrs) + # Calculate dynamic block parameters. + # Start with configuration file defaults. + self.parameters = AttrDict() + for name in params: + self.parameters[name] = getattr(self,name) + # Load the selected style attributes. + posattrs = self.posattrs + if posattrs and posattrs[0] == 'style': + # Positional attribute style has highest precedence. + style = self.attributes.get('1') + else: + style = None + if not style: + # Use explicit style attribute, fall back to default style. + style = self.attributes.get('style',self.style) + if style: + if not is_name(style): + message.error('illegal style name: %s' % style) + style = self.style + # Lists have implicit styles and do their own style checks. + elif style not in self.styles and not isinstance(self,List): + message.warning('missing style: [%s]: %s' % (self.name,style)) + style = self.style + if style in self.styles: + self.attributes['style'] = style + for k,v in self.styles[style].items(): + if k == 'posattrs': + posattrs = v + elif k in params: + self.parameters[k] = v + elif not k in self.attributes: + # Style attributes don't take precedence over explicit. + self.attributes[k] = v + # Set named positional attributes. + for i,v in enumerate(posattrs): + if str(i+1) in self.attributes: + self.attributes[v] = self.attributes[str(i+1)] + # Override config and style attributes with attribute list attributes. + self.update_parameters(attrs) + check_array_parameter('options') + check_array_parameter('presubs') + check_array_parameter('postsubs') + +class AbstractBlocks: + """List of block definitions.""" + PREFIX = '' # Conf file section name prefix set in derived classes. + BLOCK_TYPE = None # Block type set in derived classes. + def __init__(self): + self.current=None + self.blocks = [] # List of Block objects. + self.default = None # Default Block. + self.delimiters = None # Combined delimiters regular expression. + def load(self,sections): + """Load block definition from 'sections' dictionary.""" + for k in sections.keys(): + if re.match(r'^'+ self.PREFIX + r'.+$',k): + d = {} + parse_entries(sections.get(k,()),d) + for b in self.blocks: + if b.name == k: + break + else: + b = self.BLOCK_TYPE() + self.blocks.append(b) + try: + b.load(k,d) + except EAsciiDoc,e: + raise EAsciiDoc,'[%s] %s' % (k,str(e)) + def dump(self): + for b in self.blocks: + b.dump() + def isnext(self): + for b in self.blocks: + if b.isnext(): + self.current = b + return True; + return False + def validate(self): + """Validate the block definitions.""" + # Validate delimiters and build combined lists delimiter pattern. + delimiters = [] + for b in self.blocks: + assert b.__class__ is self.BLOCK_TYPE + b.validate() + if b.delimiter: + delimiters.append(b.delimiter) + self.delimiters = re_join(delimiters) + +class Paragraph(AbstractBlock): + def __init__(self): + AbstractBlock.__init__(self) + self.text=None # Text in first line of paragraph. + def load(self,name,entries): + AbstractBlock.load(self,name,entries) + def dump(self): + AbstractBlock.dump(self) + write = lambda s: sys.stdout.write('%s%s' % (s,writer.newline)) + write('') + def isnext(self): + result = AbstractBlock.isnext(self) + if result: + self.text = self.mo.groupdict().get('text') + return result + def translate(self): + AbstractBlock.translate(self) + attrs = self.mo.groupdict().copy() + if 'text' in attrs: del attrs['text'] + BlockTitle.consume(attrs) + AttributeList.consume(attrs) + self.merge_attributes(attrs) + reader.read() # Discard (already parsed item first line). + body = reader.read_until(paragraphs.terminators) + body = [self.text] + list(body) + presubs = self.parameters.presubs + postsubs = self.parameters.postsubs + if document.attributes.get('plaintext') is None: + body = Lex.set_margin(body) # Move body to left margin. + body = Lex.subs(body,presubs) + template = self.parameters.template + template = subs_attrs(template,attrs) + stag = config.section2tags(template, self.attributes,skipend=True)[0] + if self.parameters.filter: + body = filter_lines(self.parameters.filter,body,self.attributes) + body = Lex.subs(body,postsubs) + etag = config.section2tags(template, self.attributes,skipstart=True)[1] + # Write start tag, content, end tag. + writer.write(dovetail_tags(stag,body,etag),trace='paragraph') + +class Paragraphs(AbstractBlocks): + """List of paragraph definitions.""" + BLOCK_TYPE = Paragraph + PREFIX = 'paradef-' + def __init__(self): + AbstractBlocks.__init__(self) + self.terminators=None # List of compiled re's. + def initialize(self): + self.terminators = [ + re.compile(r'^\+$|^$'), + re.compile(AttributeList.pattern), + re.compile(blocks.delimiters), + re.compile(tables.delimiters), + re.compile(tables_OLD.delimiters), + ] + def load(self,sections): + AbstractBlocks.load(self,sections) + def validate(self): + AbstractBlocks.validate(self) + # Check we have a default paragraph definition, put it last in list. + for b in self.blocks: + if b.name == 'paradef-default': + self.blocks.append(b) + self.default = b + self.blocks.remove(b) + break + else: + raise EAsciiDoc,'missing section: [paradef-default]' + +class List(AbstractBlock): + NUMBER_STYLES= ('arabic','loweralpha','upperalpha','lowerroman', + 'upperroman') + def __init__(self): + AbstractBlock.__init__(self) + self.CONF_ENTRIES += ('type','tags') + self.PARAM_NAMES += ('tags',) + # tabledef conf file parameters. + self.type=None + self.tags=None # Name of listtags-<tags> conf section. + # Calculated parameters. + self.tag=None # Current tags AttrDict. + self.label=None # List item label (labeled lists). + self.text=None # Text in first line of list item. + self.index=None # Matched delimiter 'index' group (numbered lists). + self.type=None # List type ('numbered','bulleted','labeled'). + self.ordinal=None # Current list item ordinal number (1..) + self.number_style=None # Current numbered list style ('arabic'..) + def load(self,name,entries): + AbstractBlock.load(self,name,entries) + def dump(self): + AbstractBlock.dump(self) + write = lambda s: sys.stdout.write('%s%s' % (s,writer.newline)) + write('type='+self.type) + write('tags='+self.tags) + write('') + def validate(self): + AbstractBlock.validate(self) + tags = [self.tags] + tags += [s['tags'] for s in self.styles.values() if 'tags' in s] + for t in tags: + if t not in lists.tags: + self.error('missing section: [listtags-%s]' % t,halt=True) + def isnext(self): + result = AbstractBlock.isnext(self) + if result: + self.label = self.mo.groupdict().get('label') + self.text = self.mo.groupdict().get('text') + self.index = self.mo.groupdict().get('index') + return result + def translate_entry(self): + assert self.type == 'labeled' + entrytag = subs_tag(self.tag.entry, self.attributes) + labeltag = subs_tag(self.tag.label, self.attributes) + writer.write(entrytag[0],trace='list entry open') + writer.write(labeltag[0],trace='list label open') + # Write labels. + while Lex.next() is self: + reader.read() # Discard (already parsed item first line). + writer.write_tag(self.tag.term, [self.label], + self.presubs, self.attributes,trace='list term') + if self.text: break + writer.write(labeltag[1],trace='list label close') + # Write item text. + self.translate_item() + writer.write(entrytag[1],trace='list entry close') + def translate_item(self): + if self.type == 'callout': + self.attributes['coids'] = calloutmap.calloutids(self.ordinal) + itemtag = subs_tag(self.tag.item, self.attributes) + writer.write(itemtag[0],trace='list item open') + # Write ItemText. + text = reader.read_until(lists.terminators) + if self.text: + text = [self.text] + list(text) + if text: + writer.write_tag(self.tag.text, text, self.presubs, self.attributes,trace='list text') + # Process explicit and implicit list item continuations. + while True: + continuation = reader.read_next() == '+' + if continuation: reader.read() # Discard continuation line. + while Lex.next() in (BlockTitle,AttributeList): + # Consume continued element title and attributes. + Lex.next().translate() + if not continuation and BlockTitle.title: + # Titled elements terminate the list. + break + next = Lex.next() + if next in lists.open: + break + elif isinstance(next,List): + next.translate() + elif isinstance(next,Paragraph) and 'listelement' in next.options: + next.translate() + elif continuation: + # This is where continued elements are processed. + if next is Title: + message.error('section title not allowed in list item',halt=True) + next.translate() + else: + break + writer.write(itemtag[1],trace='list item close') + + @staticmethod + def calc_style(index): + """Return the numbered list style ('arabic'...) of the list item index. + Return None if unrecognized style.""" + if re.match(r'^\d+[\.>]$', index): + style = 'arabic' + elif re.match(r'^[ivx]+\)$', index): + style = 'lowerroman' + elif re.match(r'^[IVX]+\)$', index): + style = 'upperroman' + elif re.match(r'^[a-z]\.$', index): + style = 'loweralpha' + elif re.match(r'^[A-Z]\.$', index): + style = 'upperalpha' + else: + assert False + return style + + @staticmethod + def calc_index(index,style): + """Return the ordinal number of (1...) of the list item index + for the given list style.""" + def roman_to_int(roman): + roman = roman.lower() + digits = {'i':1,'v':5,'x':10} + result = 0 + for i in range(len(roman)): + digit = digits[roman[i]] + # If next digit is larger this digit is negative. + if i+1 < len(roman) and digits[roman[i+1]] > digit: + result -= digit + else: + result += digit + return result + index = index[:-1] + if style == 'arabic': + ordinal = int(index) + elif style == 'lowerroman': + ordinal = roman_to_int(index) + elif style == 'upperroman': + ordinal = roman_to_int(index) + elif style == 'loweralpha': + ordinal = ord(index) - ord('a') + 1 + elif style == 'upperalpha': + ordinal = ord(index) - ord('A') + 1 + else: + assert False + return ordinal + + def check_index(self): + """Check calculated self.ordinal (1,2,...) against the item number + in the document (self.index) and check the number style is the same as + the first item (self.number_style).""" + assert self.type in ('numbered','callout') + if self.index: + style = self.calc_style(self.index) + if style != self.number_style: + message.warning('list item style: expected %s got %s' % + (self.number_style,style), offset=1) + ordinal = self.calc_index(self.index,style) + if ordinal != self.ordinal: + message.warning('list item index: expected %s got %s' % + (self.ordinal,ordinal), offset=1) + + def check_tags(self): + """ Check that all necessary tags are present. """ + tags = set(Lists.TAGS) + if self.type != 'labeled': + tags = tags.difference(['entry','label','term']) + missing = tags.difference(self.tag.keys()) + if missing: + self.error('missing tag(s): %s' % ','.join(missing), halt=True) + def translate(self): + AbstractBlock.translate(self) + if self.short_name() in ('bibliography','glossary','qanda'): + message.deprecated('old %s list syntax' % self.short_name()) + lists.open.append(self) + attrs = self.mo.groupdict().copy() + for k in ('label','text','index'): + if k in attrs: del attrs[k] + if self.index: + # Set the numbering style from first list item. + attrs['style'] = self.calc_style(self.index) + BlockTitle.consume(attrs) + AttributeList.consume(attrs) + self.merge_attributes(attrs,['tags']) + if self.type in ('numbered','callout'): + self.number_style = self.attributes.get('style') + if self.number_style not in self.NUMBER_STYLES: + message.error('illegal numbered list style: %s' % self.number_style) + # Fall back to default style. + self.attributes['style'] = self.number_style = self.style + self.tag = lists.tags[self.parameters.tags] + self.check_tags() + if 'width' in self.attributes: + # Set horizontal list 'labelwidth' and 'itemwidth' attributes. + v = str(self.attributes['width']) + mo = re.match(r'^(\d{1,2})%?$',v) + if mo: + labelwidth = int(mo.group(1)) + self.attributes['labelwidth'] = str(labelwidth) + self.attributes['itemwidth'] = str(100-labelwidth) + else: + self.error('illegal attribute value: width="%s"' % v) + stag,etag = subs_tag(self.tag.list, self.attributes) + if stag: + writer.write(stag,trace='list open') + self.ordinal = 0 + # Process list till list syntax changes or there is a new title. + while Lex.next() is self and not BlockTitle.title: + self.ordinal += 1 + document.attributes['listindex'] = str(self.ordinal) + if self.type in ('numbered','callout'): + self.check_index() + if self.type in ('bulleted','numbered','callout'): + reader.read() # Discard (already parsed item first line). + self.translate_item() + elif self.type == 'labeled': + self.translate_entry() + else: + raise AssertionError,'illegal [%s] list type' % self.name + if etag: + writer.write(etag,trace='list close') + if self.type == 'callout': + calloutmap.validate(self.ordinal) + calloutmap.listclose() + lists.open.pop() + if len(lists.open): + document.attributes['listindex'] = str(lists.open[-1].ordinal) + +class Lists(AbstractBlocks): + """List of List objects.""" + BLOCK_TYPE = List + PREFIX = 'listdef-' + TYPES = ('bulleted','numbered','labeled','callout') + TAGS = ('list', 'entry','item','text', 'label','term') + def __init__(self): + AbstractBlocks.__init__(self) + self.open = [] # A stack of the current and parent lists. + self.tags={} # List tags dictionary. Each entry is a tags AttrDict. + self.terminators=None # List of compiled re's. + def initialize(self): + self.terminators = [ + re.compile(r'^\+$|^$'), + re.compile(AttributeList.pattern), + re.compile(lists.delimiters), + re.compile(blocks.delimiters), + re.compile(tables.delimiters), + re.compile(tables_OLD.delimiters), + ] + def load(self,sections): + AbstractBlocks.load(self,sections) + self.load_tags(sections) + def load_tags(self,sections): + """ + Load listtags-* conf file sections to self.tags. + """ + for section in sections.keys(): + mo = re.match(r'^listtags-(?P<name>\w+)$',section) + if mo: + name = mo.group('name') + if name in self.tags: + d = self.tags[name] + else: + d = AttrDict() + parse_entries(sections.get(section,()),d) + for k in d.keys(): + if k not in self.TAGS: + message.warning('[%s] contains illegal list tag: %s' % + (section,k)) + self.tags[name] = d + def validate(self): + AbstractBlocks.validate(self) + for b in self.blocks: + # Check list has valid type. + if not b.type in Lists.TYPES: + raise EAsciiDoc,'[%s] illegal type' % b.name + b.validate() + def dump(self): + AbstractBlocks.dump(self) + for k,v in self.tags.items(): + dump_section('listtags-'+k, v) + + +class DelimitedBlock(AbstractBlock): + def __init__(self): + AbstractBlock.__init__(self) + def load(self,name,entries): + AbstractBlock.load(self,name,entries) + def dump(self): + AbstractBlock.dump(self) + write = lambda s: sys.stdout.write('%s%s' % (s,writer.newline)) + write('') + def isnext(self): + return AbstractBlock.isnext(self) + def translate(self): + AbstractBlock.translate(self) + reader.read() # Discard delimiter. + attrs = {} + if self.short_name() != 'comment': + BlockTitle.consume(attrs) + AttributeList.consume(attrs) + self.merge_attributes(attrs) + options = self.parameters.options + if 'skip' in options: + reader.read_until(self.delimiter,same_file=True) + elif safe() and self.name == 'blockdef-backend': + message.unsafe('Backend Block') + reader.read_until(self.delimiter,same_file=True) + else: + template = self.parameters.template + template = subs_attrs(template,attrs) + name = self.short_name()+' block' + if 'sectionbody' in options: + # The body is treated like a section body. + stag,etag = config.section2tags(template,self.attributes) + writer.write(stag,trace=name+' open') + Section.translate_body(self) + writer.write(etag,trace=name+' close') + else: + stag = config.section2tags(template,self.attributes,skipend=True)[0] + body = reader.read_until(self.delimiter,same_file=True) + presubs = self.parameters.presubs + postsubs = self.parameters.postsubs + body = Lex.subs(body,presubs) + if self.parameters.filter: + body = filter_lines(self.parameters.filter,body,self.attributes) + body = Lex.subs(body,postsubs) + # Write start tag, content, end tag. + etag = config.section2tags(template,self.attributes,skipstart=True)[1] + writer.write(dovetail_tags(stag,body,etag),trace=name) + trace(self.short_name()+' block close',etag) + if reader.eof(): + self.error('missing closing delimiter',self.start) + else: + delimiter = reader.read() # Discard delimiter line. + assert re.match(self.delimiter,delimiter) + +class DelimitedBlocks(AbstractBlocks): + """List of delimited blocks.""" + BLOCK_TYPE = DelimitedBlock + PREFIX = 'blockdef-' + def __init__(self): + AbstractBlocks.__init__(self) + def load(self,sections): + """Update blocks defined in 'sections' dictionary.""" + AbstractBlocks.load(self,sections) + def validate(self): + AbstractBlocks.validate(self) + +class Column: + """Table column.""" + def __init__(self, width=None, align_spec=None, style=None): + self.width = width or '1' + self.halign, self.valign = Table.parse_align_spec(align_spec) + self.style = style # Style name or None. + # Calculated attribute values. + self.abswidth = None # 1.. (page units). + self.pcwidth = None # 1..99 (percentage). + +class Cell: + def __init__(self, data, span_spec=None, align_spec=None, style=None): + self.data = data + self.span, self.vspan = Table.parse_span_spec(span_spec) + self.halign, self.valign = Table.parse_align_spec(align_spec) + self.style = style + def __repr__(self): + return '<Cell: %d.%d %s.%s %s "%s">' % ( + self.span, self.vspan, + self.halign, self.valign, + self.style or '', + self.data) + +class Table(AbstractBlock): + ALIGN = {'<':'left', '>':'right', '^':'center'} + VALIGN = {'<':'top', '>':'bottom', '^':'middle'} + FORMATS = ('psv','csv','dsv') + SEPARATORS = dict( + csv=',', + dsv=r':|\n', + # The count and align group matches are not exact. + psv=r'((?<!\S)((?P<span>[\d.]+)(?P<op>[*+]))?(?P<align>[<\^>.]{,3})?(?P<style>[a-z])?)?\|' + ) + def __init__(self): + AbstractBlock.__init__(self) + self.CONF_ENTRIES += ('format','tags','separator') + # tabledef conf file parameters. + self.format='psv' + self.separator=None + self.tags=None # Name of tabletags-<tags> conf section. + # Calculated parameters. + self.abswidth=None # 1.. (page units). + self.pcwidth = None # 1..99 (percentage). + self.rows=[] # Parsed rows, each row is a list of Cells. + self.columns=[] # List of Columns. + @staticmethod + def parse_align_spec(align_spec): + """ + Parse AsciiDoc cell alignment specifier and return 2-tuple with + horizonatal and vertical alignment names. Unspecified alignments + set to None. + """ + result = (None, None) + if align_spec: + mo = re.match(r'^([<\^>])?(\.([<\^>]))?$', align_spec) + if mo: + result = (Table.ALIGN.get(mo.group(1)), + Table.VALIGN.get(mo.group(3))) + return result + @staticmethod + def parse_span_spec(span_spec): + """ + Parse AsciiDoc cell span specifier and return 2-tuple with horizonatal + and vertical span counts. Set default values (1,1) if not + specified. + """ + result = (None, None) + if span_spec: + mo = re.match(r'^(\d+)?(\.(\d+))?$', span_spec) + if mo: + result = (mo.group(1) and int(mo.group(1)), + mo.group(3) and int(mo.group(3))) + return (result[0] or 1, result[1] or 1) + def load(self,name,entries): + AbstractBlock.load(self,name,entries) + def dump(self): + AbstractBlock.dump(self) + write = lambda s: sys.stdout.write('%s%s' % (s,writer.newline)) + write('format='+self.format) + write('') + def validate(self): + AbstractBlock.validate(self) + if self.format not in Table.FORMATS: + self.error('illegal format=%s' % self.format,halt=True) + self.tags = self.tags or 'default' + tags = [self.tags] + tags += [s['tags'] for s in self.styles.values() if 'tags' in s] + for t in tags: + if t not in tables.tags: + self.error('missing section: [tabletags-%s]' % t,halt=True) + if self.separator: + # Evaluate escape characters. + self.separator = eval('"'+self.separator+'"') + #TODO: Move to class Tables + # Check global table parameters. + elif config.pagewidth is None: + self.error('missing [miscellaneous] entry: pagewidth') + elif config.pageunits is None: + self.error('missing [miscellaneous] entry: pageunits') + def validate_attributes(self): + """Validate and parse table attributes.""" + # Set defaults. + format = self.format + tags = self.tags + separator = self.separator + abswidth = float(config.pagewidth) + pcwidth = 100.0 + for k,v in self.attributes.items(): + if k == 'format': + if v not in self.FORMATS: + self.error('illegal %s=%s' % (k,v)) + else: + format = v + elif k == 'tags': + if v not in tables.tags: + self.error('illegal %s=%s' % (k,v)) + else: + tags = v + elif k == 'separator': + separator = v + elif k == 'width': + if not re.match(r'^\d{1,3}%$',v) or int(v[:-1]) > 100: + self.error('illegal %s=%s' % (k,v)) + else: + abswidth = float(v[:-1])/100 * config.pagewidth + pcwidth = float(v[:-1]) + # Calculate separator if it has not been specified. + if not separator: + separator = Table.SEPARATORS[format] + if format == 'csv': + if len(separator) > 1: + self.error('illegal csv separator=%s' % separator) + separator = ',' + else: + if not is_re(separator): + self.error('illegal regular expression: separator=%s' % + separator) + self.parameters.format = format + self.parameters.tags = tags + self.parameters.separator = separator + self.abswidth = abswidth + self.pcwidth = pcwidth + def get_tags(self,params): + tags = self.get_param('tags',params) + assert(tags and tags in tables.tags) + return tables.tags[tags] + def get_style(self,prefix): + """ + Return the style dictionary whose name starts with 'prefix'. + """ + if prefix is None: + return None + names = self.styles.keys() + names.sort() + for name in names: + if name.startswith(prefix): + return self.styles[name] + else: + self.error('missing style: %s*' % prefix) + return None + def parse_cols(self, cols, halign, valign): + """ + Build list of column objects from table 'cols', 'halign' and 'valign' + attributes. + """ + # [<multiplier>*][<align>][<width>][<style>] + COLS_RE1 = r'^((?P<count>\d+)\*)?(?P<align>[<\^>.]{,3})?(?P<width>\d+%?)?(?P<style>[a-z]\w*)?$' + # [<multiplier>*][<width>][<align>][<style>] + COLS_RE2 = r'^((?P<count>\d+)\*)?(?P<width>\d+%?)?(?P<align>[<\^>.]{,3})?(?P<style>[a-z]\w*)?$' + reo1 = re.compile(COLS_RE1) + reo2 = re.compile(COLS_RE2) + cols = str(cols) + if re.match(r'^\d+$',cols): + for i in range(int(cols)): + self.columns.append(Column()) + else: + for col in re.split(r'\s*,\s*',cols): + mo = reo1.match(col) + if not mo: + mo = reo2.match(col) + if mo: + count = int(mo.groupdict().get('count') or 1) + for i in range(count): + self.columns.append( + Column(mo.group('width'), mo.group('align'), + self.get_style(mo.group('style'))) + ) + else: + self.error('illegal column spec: %s' % col,self.start) + # Set column (and indirectly cell) default alignments. + for col in self.columns: + col.halign = col.halign or halign or document.attributes.get('halign') or 'left' + col.valign = col.valign or valign or document.attributes.get('valign') or 'top' + # Validate widths and calculate missing widths. + n = 0; percents = 0; props = 0 + for col in self.columns: + if col.width: + if col.width[-1] == '%': percents += int(col.width[:-1]) + else: props += int(col.width) + n += 1 + if percents > 0 and props > 0: + self.error('mixed percent and proportional widths: %s' + % cols,self.start) + pcunits = percents > 0 + # Fill in missing widths. + if n < len(self.columns) and percents < 100: + if pcunits: + width = float(100 - percents)/float(len(self.columns) - n) + else: + width = 1 + for col in self.columns: + if not col.width: + if pcunits: + col.width = str(int(width))+'%' + percents += width + else: + col.width = str(width) + props += width + # Calculate column alignment and absolute and percent width values. + percents = 0 + for col in self.columns: + if pcunits: + col.pcwidth = float(col.width[:-1]) + else: + col.pcwidth = (float(col.width)/props)*100 + col.abswidth = self.abswidth * (col.pcwidth/100) + if config.pageunits in ('cm','mm','in','em'): + col.abswidth = '%.2f' % round(col.abswidth,2) + else: + col.abswidth = '%d' % round(col.abswidth) + percents += col.pcwidth + col.pcwidth = int(col.pcwidth) + if round(percents) > 100: + self.error('total width exceeds 100%%: %s' % cols,self.start) + elif round(percents) < 100: + self.error('total width less than 100%%: %s' % cols,self.start) + def build_colspecs(self): + """ + Generate column related substitution attributes. + """ + cols = [] + i = 1 + for col in self.columns: + colspec = self.get_tags(col.style).colspec + if colspec: + self.attributes['halign'] = col.halign + self.attributes['valign'] = col.valign + self.attributes['colabswidth'] = col.abswidth + self.attributes['colpcwidth'] = col.pcwidth + self.attributes['colnumber'] = str(i) + s = subs_attrs(colspec, self.attributes) + if not s: + message.warning('colspec dropped: contains undefined attribute') + else: + cols.append(s) + i += 1 + if cols: + self.attributes['colspecs'] = writer.newline.join(cols) + def parse_rows(self, text): + """ + Parse the table source text into self.rows (a list of rows, each row + is a list of Cells. + """ + reserved = {} # Cols reserved by rowspans (indexed by row number). + if self.parameters.format in ('psv','dsv'): + ri = 0 # Current row index 0.. + cells = self.parse_psv_dsv(text) + row = [] + ci = 0 # Column counter 0..colcount + for cell in cells: + colcount = len(self.columns) - reserved.get(ri,0) + if cell.vspan > 1: + # Reserve spanned columns from ensuing rows. + for i in range(1, cell.vspan): + reserved[ri+i] = reserved.get(ri+i, 0) + cell.span + ci += cell.span + if ci <= colcount: + row.append(cell) + if ci >= colcount: + self.rows.append(row) + ri += 1 + row = [] + ci = 0 + if ci > colcount: + message.warning('table row %d: span exceeds number of columns' + % ri) + elif self.parameters.format == 'csv': + self.rows = self.parse_csv(text) + else: + assert True,'illegal table format' + # Check that all row spans match. + for ri,row in enumerate(self.rows): + row_span = 0 + for cell in row: + row_span += cell.span + row_span += reserved.get(ri,0) + if ri == 0: + header_span = row_span + if row_span < header_span: + message.warning('table row %d: does not span all columns' % (ri+1)) + if row_span > header_span: + message.warning('table row %d: exceeds columns span' % (ri+1)) + # Check that now row spans exceed the number of rows. + if len([x for x in reserved.keys() if x >= len(self.rows)]) > 0: + message.warning('one or more cell spans exceed the available rows') + def subs_rows(self, rows, rowtype='body'): + """ + Return a string of output markup from a list of rows, each row + is a list of raw data text. + """ + tags = tables.tags[self.parameters.tags] + if rowtype == 'header': + rtag = tags.headrow + elif rowtype == 'footer': + rtag = tags.footrow + else: + rtag = tags.bodyrow + result = [] + stag,etag = subs_tag(rtag,self.attributes) + for row in rows: + result.append(stag) + result += self.subs_row(row,rowtype) + result.append(etag) + return writer.newline.join(result) + def subs_row(self, row, rowtype): + """ + Substitute the list of Cells using the data tag. + Returns a list of marked up table cell elements. + """ + result = [] + i = 0 + for cell in row: + if i >= len(self.columns): + break # Skip cells outside the header width. + col = self.columns[i] + self.attributes['halign'] = cell.halign or col.halign + self.attributes['valign'] = cell.valign or col.valign + self.attributes['colabswidth'] = col.abswidth + self.attributes['colpcwidth'] = col.pcwidth + self.attributes['colnumber'] = str(i+1) + self.attributes['colspan'] = str(cell.span) + self.attributes['colstart'] = self.attributes['colnumber'] + self.attributes['colend'] = str(i+cell.span) + self.attributes['rowspan'] = str(cell.vspan) + self.attributes['morerows'] = str(cell.vspan-1) + # Fill missing column data with blanks. + if i > len(self.columns) - 1: + data = '' + else: + data = cell.data + if rowtype == 'header': + # Use table style unless overriden by cell style. + colstyle = cell.style + else: + # If the cell style is not defined use the column style. + colstyle = cell.style or col.style + tags = self.get_tags(colstyle) + presubs,postsubs = self.get_subs(colstyle) + data = [data] + data = Lex.subs(data, presubs) + data = filter_lines(self.get_param('filter',colstyle), + data, self.attributes) + data = Lex.subs(data, postsubs) + if rowtype != 'header': + ptag = tags.paragraph + if ptag: + stag,etag = subs_tag(ptag,self.attributes) + text = '\n'.join(data).strip() + data = [] + for para in re.split(r'\n{2,}',text): + data += dovetail_tags([stag],para.split('\n'),[etag]) + if rowtype == 'header': + dtag = tags.headdata + elif rowtype == 'footer': + dtag = tags.footdata + else: + dtag = tags.bodydata + stag,etag = subs_tag(dtag,self.attributes) + result = result + dovetail_tags([stag],data,[etag]) + i += cell.span + return result + def parse_csv(self,text): + """ + Parse the table source text and return a list of rows, each row + is a list of Cells. + """ + import StringIO + import csv + rows = [] + rdr = csv.reader(StringIO.StringIO('\r\n'.join(text)), + delimiter=self.parameters.separator, skipinitialspace=True) + try: + for row in rdr: + rows.append([Cell(data) for data in row]) + except Exception: + self.error('csv parse error: %s' % row) + return rows + def parse_psv_dsv(self,text): + """ + Parse list of PSV or DSV table source text lines and return a list of + Cells. + """ + def append_cell(data, span_spec, op, align_spec, style): + op = op or '+' + if op == '*': # Cell multiplier. + span = Table.parse_span_spec(span_spec)[0] + for i in range(span): + cells.append(Cell(data, '1', align_spec, style)) + elif op == '+': # Column spanner. + cells.append(Cell(data, span_spec, align_spec, style)) + else: + self.error('illegal table cell operator') + text = '\n'.join(text) + separator = '(?msu)'+self.parameters.separator + format = self.parameters.format + start = 0 + span = None + op = None + align = None + style = None + cells = [] + data = '' + for mo in re.finditer(separator,text): + data += text[start:mo.start()] + if data.endswith('\\'): + data = data[:-1]+mo.group() # Reinstate escaped separators. + else: + append_cell(data, span, op, align, style) + span = mo.groupdict().get('span') + op = mo.groupdict().get('op') + align = mo.groupdict().get('align') + style = mo.groupdict().get('style') + if style: + style = self.get_style(style) + data = '' + start = mo.end() + # Last cell follows final separator. + data += text[start:] + append_cell(data, span, op, align, style) + # We expect a dummy blank item preceeding first PSV cell. + if format == 'psv': + if cells[0].data.strip() != '': + self.error('missing leading separator: %s' % separator, + self.start) + else: + cells.pop(0) + return cells + def translate(self): + AbstractBlock.translate(self) + reader.read() # Discard delimiter. + # Reset instance specific properties. + self.columns = [] + self.rows = [] + attrs = {} + BlockTitle.consume(attrs) + # Mix in document attribute list. + AttributeList.consume(attrs) + self.merge_attributes(attrs) + self.validate_attributes() + # Add global and calculated configuration parameters. + self.attributes['pagewidth'] = config.pagewidth + self.attributes['pageunits'] = config.pageunits + self.attributes['tableabswidth'] = int(self.abswidth) + self.attributes['tablepcwidth'] = int(self.pcwidth) + # Read the entire table. + text = reader.read_until(self.delimiter) + if reader.eof(): + self.error('missing closing delimiter',self.start) + else: + delimiter = reader.read() # Discard closing delimiter. + assert re.match(self.delimiter,delimiter) + if len(text) == 0: + message.warning('[%s] table is empty' % self.name) + return + cols = attrs.get('cols') + if not cols: + # Calculate column count from number of items in first line. + if self.parameters.format == 'csv': + cols = text[0].count(self.parameters.separator) + 1 + else: + cols = 0 + for cell in self.parse_psv_dsv(text[:1]): + cols += cell.span + self.parse_cols(cols, attrs.get('halign'), attrs.get('valign')) + # Set calculated attributes. + self.attributes['colcount'] = len(self.columns) + self.build_colspecs() + self.parse_rows(text) + # The 'rowcount' attribute is used by the experimental LaTeX backend. + self.attributes['rowcount'] = str(len(self.rows)) + # Generate headrows, footrows, bodyrows. + # Headrow, footrow and bodyrow data replaces same named attributes in + # the table markup template. In order to ensure this data does not get + # a second attribute substitution (which would interfere with any + # already substituted inline passthroughs) unique placeholders are used + # (the tab character does not appear elsewhere since it is expanded on + # input) which are replaced after template attribute substitution. + headrows = footrows = bodyrows = None + if self.rows and 'header' in self.parameters.options: + headrows = self.subs_rows(self.rows[0:1],'header') + self.attributes['headrows'] = '\x07headrows\x07' + self.rows = self.rows[1:] + if self.rows and 'footer' in self.parameters.options: + footrows = self.subs_rows( self.rows[-1:], 'footer') + self.attributes['footrows'] = '\x07footrows\x07' + self.rows = self.rows[:-1] + if self.rows: + bodyrows = self.subs_rows(self.rows) + self.attributes['bodyrows'] = '\x07bodyrows\x07' + table = subs_attrs(config.sections[self.parameters.template], + self.attributes) + table = writer.newline.join(table) + # Before we finish replace the table head, foot and body place holders + # with the real data. + if headrows: + table = table.replace('\x07headrows\x07', headrows, 1) + if footrows: + table = table.replace('\x07footrows\x07', footrows, 1) + if bodyrows: + table = table.replace('\x07bodyrows\x07', bodyrows, 1) + writer.write(table,trace='table') + +class Tables(AbstractBlocks): + """List of tables.""" + BLOCK_TYPE = Table + PREFIX = 'tabledef-' + TAGS = ('colspec', 'headrow','footrow','bodyrow', + 'headdata','footdata', 'bodydata','paragraph') + def __init__(self): + AbstractBlocks.__init__(self) + # Table tags dictionary. Each entry is a tags dictionary. + self.tags={} + def load(self,sections): + AbstractBlocks.load(self,sections) + self.load_tags(sections) + def load_tags(self,sections): + """ + Load tabletags-* conf file sections to self.tags. + """ + for section in sections.keys(): + mo = re.match(r'^tabletags-(?P<name>\w+)$',section) + if mo: + name = mo.group('name') + if name in self.tags: + d = self.tags[name] + else: + d = AttrDict() + parse_entries(sections.get(section,()),d) + for k in d.keys(): + if k not in self.TAGS: + message.warning('[%s] contains illegal table tag: %s' % + (section,k)) + self.tags[name] = d + def validate(self): + AbstractBlocks.validate(self) + # Check we have a default table definition, + for i in range(len(self.blocks)): + if self.blocks[i].name == 'tabledef-default': + default = self.blocks[i] + break + else: + raise EAsciiDoc,'missing section: [tabledef-default]' + # Propagate defaults to unspecified table parameters. + for b in self.blocks: + if b is not default: + if b.format is None: b.format = default.format + if b.template is None: b.template = default.template + # Check tags and propagate default tags. + if not 'default' in self.tags: + raise EAsciiDoc,'missing section: [tabletags-default]' + default = self.tags['default'] + for tag in ('bodyrow','bodydata','paragraph'): # Mandatory default tags. + if tag not in default: + raise EAsciiDoc,'missing [tabletags-default] entry: %s' % tag + for t in self.tags.values(): + if t is not default: + if t.colspec is None: t.colspec = default.colspec + if t.headrow is None: t.headrow = default.headrow + if t.footrow is None: t.footrow = default.footrow + if t.bodyrow is None: t.bodyrow = default.bodyrow + if t.headdata is None: t.headdata = default.headdata + if t.footdata is None: t.footdata = default.footdata + if t.bodydata is None: t.bodydata = default.bodydata + if t.paragraph is None: t.paragraph = default.paragraph + # Use body tags if header and footer tags are not specified. + for t in self.tags.values(): + if not t.headrow: t.headrow = t.bodyrow + if not t.footrow: t.footrow = t.bodyrow + if not t.headdata: t.headdata = t.bodydata + if not t.footdata: t.footdata = t.bodydata + # Check table definitions are valid. + for b in self.blocks: + b.validate() + def dump(self): + AbstractBlocks.dump(self) + for k,v in self.tags.items(): + dump_section('tabletags-'+k, v) + +class Macros: + # Default system macro syntax. + SYS_RE = r'(?u)^(?P<name>[\\]?\w(\w|-)*?)::(?P<target>\S*?)' + \ + r'(\[(?P<attrlist>.*?)\])$' + def __init__(self): + self.macros = [] # List of Macros. + self.current = None # The last matched block macro. + self.passthroughs = [] + # Initialize default system macro. + m = Macro() + m.pattern = self.SYS_RE + m.prefix = '+' + m.reo = re.compile(m.pattern) + self.macros.append(m) + def load(self,entries): + for entry in entries: + m = Macro() + m.load(entry) + if m.name is None: + # Delete undefined macro. + for i,m2 in enumerate(self.macros): + if m2.pattern == m.pattern: + del self.macros[i] + break + else: + message.warning('unable to delete missing macro: %s' % m.pattern) + else: + # Check for duplicates. + for m2 in self.macros: + if m2.pattern == m.pattern: + message.verbose('macro redefinition: %s%s' % (m.prefix,m.name)) + break + else: + self.macros.append(m) + def dump(self): + write = lambda s: sys.stdout.write('%s%s' % (s,writer.newline)) + write('[macros]') + # Dump all macros except the first (built-in system) macro. + for m in self.macros[1:]: + # Escape = in pattern. + macro = '%s=%s%s' % (m.pattern.replace('=',r'\='), m.prefix, m.name) + if m.subslist is not None: + macro += '[' + ','.join(m.subslist) + ']' + write(macro) + write('') + def validate(self): + # Check all named sections exist. + if config.verbose: + for m in self.macros: + if m.name and m.prefix != '+': + m.section_name() + def subs(self,text,prefix='',callouts=False): + # If callouts is True then only callout macros are processed, if False + # then all non-callout macros are processed. + result = text + for m in self.macros: + if m.prefix == prefix: + if callouts ^ (m.name != 'callout'): + result = m.subs(result) + return result + def isnext(self): + """Return matching macro if block macro is next on reader.""" + reader.skip_blank_lines() + line = reader.read_next() + if line: + for m in self.macros: + if m.prefix == '#': + if m.reo.match(line): + self.current = m + return m + return False + def match(self,prefix,name,text): + """Return re match object matching 'text' with macro type 'prefix', + macro name 'name'.""" + for m in self.macros: + if m.prefix == prefix: + mo = m.reo.match(text) + if mo: + if m.name == name: + return mo + if re.match(name,mo.group('name')): + return mo + return None + def extract_passthroughs(self,text,prefix=''): + """ Extract the passthrough text and replace with temporary + placeholders.""" + self.passthroughs = [] + for m in self.macros: + if m.has_passthrough() and m.prefix == prefix: + text = m.subs_passthroughs(text, self.passthroughs) + return text + def restore_passthroughs(self,text): + """ Replace passthough placeholders with the original passthrough + text.""" + for i,v in enumerate(self.passthroughs): + text = text.replace('\x07'+str(i)+'\x07', self.passthroughs[i]) + return text + +class Macro: + def __init__(self): + self.pattern = None # Matching regular expression. + self.name = '' # Conf file macro name (None if implicit). + self.prefix = '' # '' if inline, '+' if system, '#' if block. + self.reo = None # Compiled pattern re object. + self.subslist = [] # Default subs for macros passtext group. + def has_passthrough(self): + return self.pattern.find(r'(?P<passtext>') >= 0 + def section_name(self,name=None): + """Return macro markup template section name based on macro name and + prefix. Return None section not found.""" + assert self.prefix != '+' + if not name: + assert self.name + name = self.name + if self.prefix == '#': + suffix = '-blockmacro' + else: + suffix = '-inlinemacro' + if name+suffix in config.sections: + return name+suffix + else: + message.warning('missing macro section: [%s]' % (name+suffix)) + return None + def load(self,entry): + e = parse_entry(entry) + if e is None: + # Only the macro pattern was specified, mark for deletion. + self.name = None + self.pattern = entry + return + if not is_re(e[0]): + raise EAsciiDoc,'illegal macro regular expression: %s' % e[0] + pattern, name = e + if name and name[0] in ('+','#'): + prefix, name = name[0], name[1:] + else: + prefix = '' + # Parse passthrough subslist. + mo = re.match(r'^(?P<name>[^[]*)(\[(?P<subslist>.*)\])?$', name) + name = mo.group('name') + if name and not is_name(name): + raise EAsciiDoc,'illegal section name in macro entry: %s' % entry + subslist = mo.group('subslist') + if subslist is not None: + # Parse and validate passthrough subs. + subslist = parse_options(subslist, SUBS_OPTIONS, + 'illegal subs in macro entry: %s' % entry) + self.pattern = pattern + self.reo = re.compile(pattern) + self.prefix = prefix + self.name = name + self.subslist = subslist or [] + + def subs(self,text): + def subs_func(mo): + """Function called to perform macro substitution. + Uses matched macro regular expression object and returns string + containing the substituted macro body.""" + # Check if macro reference is escaped. + if mo.group()[0] == '\\': + return mo.group()[1:] # Strip leading backslash. + d = mo.groupdict() + # Delete groups that didn't participate in match. + for k,v in d.items(): + if v is None: del d[k] + if self.name: + name = self.name + else: + if not 'name' in d: + message.warning('missing macro name group: %s' % mo.re.pattern) + return '' + name = d['name'] + section_name = self.section_name(name) + if not section_name: + return '' + # If we're dealing with a block macro get optional block ID and + # block title. + if self.prefix == '#' and self.name != 'comment': + AttributeList.consume(d) + BlockTitle.consume(d) + # Parse macro attributes. + if 'attrlist' in d: + if d['attrlist'] in (None,''): + del d['attrlist'] + else: + if self.prefix == '': + # Unescape ] characters in inline macros. + d['attrlist'] = d['attrlist'].replace('\\]',']') + parse_attributes(d['attrlist'],d) + # Generate option attributes. + if 'options' in d: + options = parse_options(d['options'], (), + '%s: illegal option name' % name) + for option in options: + d[option+'-option'] = '' + # Substitute single quoted attribute values in block macros. + if self.prefix == '#': + AttributeList.subs(d) + if name == 'callout': + listindex =int(d['index']) + d['coid'] = calloutmap.add(listindex) + # The alt attribute is the first image macro positional attribute. + if name == 'image' and '1' in d: + d['alt'] = d['1'] + # Unescape special characters in LaTeX target file names. + if document.backend == 'latex' and 'target' in d and d['target']: + if not '0' in d: + d['0'] = d['target'] + d['target']= config.subs_specialchars_reverse(d['target']) + # BUG: We've already done attribute substitution on the macro which + # means that any escaped attribute references are now unescaped and + # will be substituted by config.subs_section() below. As a partial + # fix have withheld {0} from substitution but this kludge doesn't + # fix it for other attributes containing unescaped references. + # Passthrough macros don't have this problem. + a0 = d.get('0') + if a0: + d['0'] = chr(0) # Replace temporarily with unused character. + body = config.subs_section(section_name,d) + if len(body) == 0: + result = '' + elif len(body) == 1: + result = body[0] + else: + if self.prefix == '#': + result = writer.newline.join(body) + else: + # Internally processed inline macros use UNIX line + # separator. + result = '\n'.join(body) + if a0: + result = result.replace(chr(0), a0) + return result + + return self.reo.sub(subs_func, text) + + def translate(self): + """ Block macro translation.""" + assert self.prefix == '#' + s = reader.read() + before = s + if self.has_passthrough(): + s = macros.extract_passthroughs(s,'#') + s = subs_attrs(s) + if s: + s = self.subs(s) + if self.has_passthrough(): + s = macros.restore_passthroughs(s) + if s: + trace('macro block',before,s) + writer.write(s) + + def subs_passthroughs(self, text, passthroughs): + """ Replace macro attribute lists in text with placeholders. + Substitute and append the passthrough attribute lists to the + passthroughs list.""" + def subs_func(mo): + """Function called to perform inline macro substitution. + Uses matched macro regular expression object and returns string + containing the substituted macro body.""" + # Don't process escaped macro references. + if mo.group()[0] == '\\': + return mo.group() + d = mo.groupdict() + if not 'passtext' in d: + message.warning('passthrough macro %s: missing passtext group' % + d.get('name','')) + return mo.group() + passtext = d['passtext'] + if re.search('\x07\\d+\x07', passtext): + message.warning('nested inline passthrough') + return mo.group() + if d.get('subslist'): + if d['subslist'].startswith(':'): + message.error('block macro cannot occur here: %s' % mo.group(), + halt=True) + subslist = parse_options(d['subslist'], SUBS_OPTIONS, + 'illegal passthrough macro subs option') + else: + subslist = self.subslist + passtext = Lex.subs_1(passtext,subslist) + if passtext is None: passtext = '' + if self.prefix == '': + # Unescape ] characters in inline macros. + passtext = passtext.replace('\\]',']') + passthroughs.append(passtext) + # Tabs guarantee the placeholders are unambiguous. + result = ( + text[mo.start():mo.start('passtext')] + + '\x07' + str(len(passthroughs)-1) + '\x07' + + text[mo.end('passtext'):mo.end()] + ) + return result + + return self.reo.sub(subs_func, text) + + +class CalloutMap: + def __init__(self): + self.comap = {} # key = list index, value = callouts list. + self.calloutindex = 0 # Current callout index number. + self.listnumber = 1 # Current callout list number. + def listclose(self): + # Called when callout list is closed. + self.listnumber += 1 + self.calloutindex = 0 + self.comap = {} + def add(self,listindex): + # Add next callout index to listindex map entry. Return the callout id. + self.calloutindex += 1 + # Append the coindex to a list in the comap dictionary. + if not listindex in self.comap: + self.comap[listindex] = [self.calloutindex] + else: + self.comap[listindex].append(self.calloutindex) + return self.calloutid(self.listnumber, self.calloutindex) + @staticmethod + def calloutid(listnumber,calloutindex): + return 'CO%d-%d' % (listnumber,calloutindex) + def calloutids(self,listindex): + # Retieve list of callout indexes that refer to listindex. + if listindex in self.comap: + result = '' + for coindex in self.comap[listindex]: + result += ' ' + self.calloutid(self.listnumber,coindex) + return result.strip() + else: + message.warning('no callouts refer to list item '+str(listindex)) + return '' + def validate(self,maxlistindex): + # Check that all list indexes referenced by callouts exist. + for listindex in self.comap.keys(): + if listindex > maxlistindex: + message.warning('callout refers to non-existent list item ' + + str(listindex)) + +#--------------------------------------------------------------------------- +# Input stream Reader and output stream writer classes. +#--------------------------------------------------------------------------- + +UTF8_BOM = '\xef\xbb\xbf' + +class Reader1: + """Line oriented AsciiDoc input file reader. Processes include and + conditional inclusion system macros. Tabs are expanded and lines are right + trimmed.""" + # This class is not used directly, use Reader class instead. + READ_BUFFER_MIN = 10 # Read buffer low level. + def __init__(self): + self.f = None # Input file object. + self.fname = None # Input file name. + self.next = [] # Read ahead buffer containing + # [filename,linenumber,linetext] lists. + self.cursor = None # Last read() [filename,linenumber,linetext]. + self.tabsize = 8 # Tab expansion number of spaces. + self.parent = None # Included reader's parent reader. + self._lineno = 0 # The last line read from file object f. + self.current_depth = 0 # Current include depth. + self.max_depth = 5 # Initial maxiumum allowed include depth. + self.bom = None # Byte order mark (BOM). + self.infile = None # Saved document 'infile' attribute. + self.indir = None # Saved document 'indir' attribute. + def open(self,fname): + self.fname = fname + message.verbose('reading: '+fname) + if fname == '<stdin>': + self.f = sys.stdin + self.infile = None + self.indir = None + else: + self.f = open(fname,'rb') + self.infile = fname + self.indir = os.path.dirname(fname) + document.attributes['infile'] = self.infile + document.attributes['indir'] = self.indir + self._lineno = 0 # The last line read from file object f. + self.next = [] + # Prefill buffer by reading the first line and then pushing it back. + if Reader1.read(self): + if self.cursor[2].startswith(UTF8_BOM): + self.cursor[2] = self.cursor[2][len(UTF8_BOM):] + self.bom = UTF8_BOM + self.unread(self.cursor) + self.cursor = None + def closefile(self): + """Used by class methods to close nested include files.""" + self.f.close() + self.next = [] + def close(self): + self.closefile() + self.__init__() + def read(self, skip=False): + """Read next line. Return None if EOF. Expand tabs. Strip trailing + white space. Maintain self.next read ahead buffer. If skip=True then + conditional exclusion is active (ifdef and ifndef macros).""" + # Top up buffer. + if len(self.next) <= self.READ_BUFFER_MIN: + s = self.f.readline() + if s: + self._lineno = self._lineno + 1 + while s: + if self.tabsize != 0: + s = s.expandtabs(self.tabsize) + s = s.rstrip() + self.next.append([self.fname,self._lineno,s]) + if len(self.next) > self.READ_BUFFER_MIN: + break + s = self.f.readline() + if s: + self._lineno = self._lineno + 1 + # Return first (oldest) buffer entry. + if len(self.next) > 0: + self.cursor = self.next[0] + del self.next[0] + result = self.cursor[2] + # Check for include macro. + mo = macros.match('+',r'include[1]?',result) + if mo and not skip: + # Don't process include macro once the maximum depth is reached. + if self.current_depth >= self.max_depth: + return result + # Perform attribute substitution on include macro file name. + fname = subs_attrs(mo.group('target')) + if not fname: + return Reader1.read(self) # Return next input line. + if self.fname != '<stdin>': + fname = os.path.expandvars(os.path.expanduser(fname)) + fname = safe_filename(fname, os.path.dirname(self.fname)) + if not fname: + return Reader1.read(self) # Return next input line. + if mo.group('name') == 'include1': + if not config.dumping: + # Store the include file in memory for later + # retrieval by the {include1:} system attribute. + config.include1[fname] = [ + s.rstrip() for s in open(fname)] + return '{include1:%s}' % fname + else: + # This is a configuration dump, just pass the macro + # call through. + return result + # Parse include macro attributes. + attrs = {} + parse_attributes(mo.group('attrlist'),attrs) + # Clone self and set as parent (self assumes the role of child). + parent = Reader1() + assign(parent,self) + self.parent = parent + # Set attributes in child. + if 'tabsize' in attrs: + self.tabsize = int(validate(attrs['tabsize'], + 'int($)>=0', + 'illegal include macro tabsize argument')) + else: + self.tabsize = config.tabsize + if 'depth' in attrs: + attrs['depth'] = int(validate(attrs['depth'], + 'int($)>=1', + 'illegal include macro depth argument')) + self.max_depth = self.current_depth + attrs['depth'] + # Process included file. + self.open(fname) + self.current_depth = self.current_depth + 1 + result = Reader1.read(self) + else: + if not Reader1.eof(self): + result = Reader1.read(self) + else: + result = None + return result + def eof(self): + """Returns True if all lines have been read.""" + if len(self.next) == 0: + # End of current file. + if self.parent: + self.closefile() + assign(self,self.parent) # Restore parent reader. + document.attributes['infile'] = self.infile + document.attributes['indir'] = self.indir + return Reader1.eof(self) + else: + return True + else: + return False + def read_next(self): + """Like read() but does not advance file pointer.""" + if Reader1.eof(self): + return None + else: + return self.next[0][2] + def unread(self,cursor): + """Push the line (filename,linenumber,linetext) tuple back into the read + buffer. Note that it's up to the caller to restore the previous + cursor.""" + assert cursor + self.next.insert(0,cursor) + +class Reader(Reader1): + """ Wraps (well, sought of) Reader1 class and implements conditional text + inclusion.""" + def __init__(self): + Reader1.__init__(self) + self.depth = 0 # if nesting depth. + self.skip = False # true if we're skipping ifdef...endif. + self.skipname = '' # Name of current endif macro target. + self.skipto = -1 # The depth at which skipping is reenabled. + def read_super(self): + result = Reader1.read(self,self.skip) + if result is None and self.skip: + raise EAsciiDoc,'missing endif::%s[]' % self.skipname + return result + def read(self): + result = self.read_super() + if result is None: + return None + while self.skip: + mo = macros.match('+',r'ifdef|ifndef|ifeval|endif',result) + if mo: + name = mo.group('name') + target = mo.group('target') + attrlist = mo.group('attrlist') + if name == 'endif': + self.depth -= 1 + if self.depth < 0: + raise EAsciiDoc,'mismatched macro: %s' % result + if self.depth == self.skipto: + self.skip = False + if target and self.skipname != target: + raise EAsciiDoc,'mismatched macro: %s' % result + else: + if name in ('ifdef','ifndef'): + if not target: + raise EAsciiDoc,'missing macro target: %s' % result + if not attrlist: + self.depth += 1 + elif name == 'ifeval': + if not attrlist: + raise EAsciiDoc,'missing ifeval condition: %s' % result + self.depth += 1 + result = self.read_super() + if result is None: + return None + mo = macros.match('+',r'ifdef|ifndef|ifeval|endif',result) + if mo: + name = mo.group('name') + target = mo.group('target') + attrlist = mo.group('attrlist') + if name == 'endif': + self.depth = self.depth-1 + else: + if not target and name in ('ifdef','ifndef'): + raise EAsciiDoc,'missing macro target: %s' % result + defined = is_attr_defined(target, document.attributes) + if name == 'ifdef': + if attrlist: + if defined: return attrlist + else: + self.skip = not defined + elif name == 'ifndef': + if attrlist: + if not defined: return attrlist + else: + self.skip = defined + elif name == 'ifeval': + if not attrlist: + raise EAsciiDoc,'missing ifeval condition: %s' % result + cond = False + attrlist = subs_attrs(attrlist) + if attrlist: + try: + cond = eval(attrlist) + except Exception,e: + raise EAsciiDoc,'error evaluating ifeval condition: %s: %s' % (result, str(e)) + self.skip = not cond + if not attrlist or name == 'ifeval': + if self.skip: + self.skipto = self.depth + self.skipname = target + self.depth = self.depth+1 + result = self.read() + if result: + # Expand executable block macros. + mo = macros.match('+',r'eval|sys|sys2',result) + if mo: + action = mo.group('name') + cmd = mo.group('attrlist') + s = system(action, cmd, is_macro=True) + if s is not None: + self.cursor[2] = s # So we don't re-evaluate. + result = s + if result: + # Unescape escaped system macros. + if macros.match('+',r'\\eval|\\sys|\\sys2|\\ifdef|\\ifndef|\\endif|\\include|\\include1',result): + result = result[1:] + return result + def eof(self): + return self.read_next() is None + def read_next(self): + save_cursor = self.cursor + result = self.read() + if result is not None: + self.unread(self.cursor) + self.cursor = save_cursor + return result + def read_lines(self,count=1): + """Return tuple containing count lines.""" + result = [] + i = 0 + while i < count and not self.eof(): + result.append(self.read()) + return tuple(result) + def read_ahead(self,count=1): + """Same as read_lines() but does not advance the file pointer.""" + result = [] + putback = [] + save_cursor = self.cursor + try: + i = 0 + while i < count and not self.eof(): + result.append(self.read()) + putback.append(self.cursor) + i = i+1 + while putback: + self.unread(putback.pop()) + finally: + self.cursor = save_cursor + return tuple(result) + def skip_blank_lines(self): + reader.read_until(r'\s*\S+') + def read_until(self,terminators,same_file=False): + """Like read() but reads lines up to (but not including) the first line + that matches the terminator regular expression, regular expression + object or list of regular expression objects. If same_file is True then + the terminating pattern must occur in the file the was being read when + the routine was called.""" + if same_file: + fname = self.cursor[0] + result = [] + if not isinstance(terminators,list): + if isinstance(terminators,basestring): + terminators = [re.compile(terminators)] + else: + terminators = [terminators] + while not self.eof(): + save_cursor = self.cursor + s = self.read() + if not same_file or fname == self.cursor[0]: + for reo in terminators: + if reo.match(s): + self.unread(self.cursor) + self.cursor = save_cursor + return tuple(result) + result.append(s) + return tuple(result) + +class Writer: + """Writes lines to output file.""" + def __init__(self): + self.newline = '\r\n' # End of line terminator. + self.f = None # Output file object. + self.fname = None # Output file name. + self.lines_out = 0 # Number of lines written. + self.skip_blank_lines = False # If True don't output blank lines. + def open(self,fname,bom=None): + ''' + bom is optional byte order mark. + http://en.wikipedia.org/wiki/Byte-order_mark + ''' + self.fname = fname + if fname == '<stdout>': + self.f = sys.stdout + else: + self.f = open(fname,'wb+') + message.verbose('writing: '+writer.fname,False) + if bom: + self.f.write(bom) + self.lines_out = 0 + def close(self): + if self.fname != '<stdout>': + self.f.close() + def write_line(self, line=None): + if not (self.skip_blank_lines and (not line or not line.strip())): + self.f.write((line or '') + self.newline) + self.lines_out = self.lines_out + 1 + def write(self,*args,**kwargs): + """Iterates arguments, writes tuple and list arguments one line per + element, else writes argument as single line. If no arguments writes + blank line. If argument is None nothing is written. self.newline is + appended to each line.""" + if 'trace' in kwargs and len(args) > 0: + trace(kwargs['trace'],args[0]) + if len(args) == 0: + self.write_line() + self.lines_out = self.lines_out + 1 + else: + for arg in args: + if is_array(arg): + for s in arg: + self.write_line(s) + elif arg is not None: + self.write_line(arg) + def write_tag(self,tag,content,subs=None,d=None,**kwargs): + """Write content enveloped by tag. + Substitutions specified in the 'subs' list are perform on the + 'content'.""" + if subs is None: + subs = config.subsnormal + stag,etag = subs_tag(tag,d) + content = Lex.subs(content,subs) + if 'trace' in kwargs: + trace(kwargs['trace'],[stag]+content+[etag]) + if stag: + self.write(stag) + if content: + self.write(content) + if etag: + self.write(etag) + +#--------------------------------------------------------------------------- +# Configuration file processing. +#--------------------------------------------------------------------------- +def _subs_specialwords(mo): + """Special word substitution function called by + Config.subs_specialwords().""" + word = mo.re.pattern # The special word. + template = config.specialwords[word] # The corresponding markup template. + if not template in config.sections: + raise EAsciiDoc,'missing special word template [%s]' % template + if mo.group()[0] == '\\': + return mo.group()[1:] # Return escaped word. + args = {} + args['words'] = mo.group() # The full match string is argument 'words'. + args.update(mo.groupdict()) # Add other named match groups to the arguments. + # Delete groups that didn't participate in match. + for k,v in args.items(): + if v is None: del args[k] + lines = subs_attrs(config.sections[template],args) + if len(lines) == 0: + result = '' + elif len(lines) == 1: + result = lines[0] + else: + result = writer.newline.join(lines) + return result + +class Config: + """Methods to process configuration files.""" + # Non-template section name regexp's. + ENTRIES_SECTIONS= ('tags','miscellaneous','attributes','specialcharacters', + 'specialwords','macros','replacements','quotes','titles', + r'paradef-.+',r'listdef-.+',r'blockdef-.+',r'tabledef-.+', + r'tabletags-.+',r'listtags-.+','replacements2', + r'old_tabledef-.+') + def __init__(self): + self.sections = OrderedDict() # Keyed by section name containing + # lists of section lines. + # Command-line options. + self.verbose = False + self.header_footer = True # -s, --no-header-footer option. + # [miscellaneous] section. + self.tabsize = 8 + self.textwidth = 70 # DEPRECATED: Old tables only. + self.newline = '\r\n' + self.pagewidth = None + self.pageunits = None + self.outfilesuffix = '' + self.subsnormal = SUBS_NORMAL + self.subsverbatim = SUBS_VERBATIM + + self.tags = {} # Values contain (stag,etag) tuples. + self.specialchars = {} # Values of special character substitutions. + self.specialwords = {} # Name is special word pattern, value is macro. + self.replacements = OrderedDict() # Key is find pattern, value is + #replace pattern. + self.replacements2 = OrderedDict() + self.specialsections = {} # Name is special section name pattern, value + # is corresponding section name. + self.quotes = OrderedDict() # Values contain corresponding tag name. + self.fname = '' # Most recently loaded configuration file name. + self.conf_attrs = {} # Attributes entries from conf files. + self.cmd_attrs = {} # Attributes from command-line -a options. + self.loaded = [] # Loaded conf files. + self.include1 = {} # Holds include1::[] files for {include1:}. + self.dumping = False # True if asciidoc -c option specified. + + def init(self, cmd): + """ + Check Python version and locate the executable and configuration files + directory. + cmd is the asciidoc command or asciidoc.py path. + """ + if float(sys.version[:3]) < MIN_PYTHON_VERSION: + message.stderr('FAILED: Python 2.3 or better required') + sys.exit(1) + if not os.path.exists(cmd): + message.stderr('FAILED: Missing asciidoc command: %s' % cmd) + sys.exit(1) + global APP_FILE + APP_FILE = os.path.realpath(cmd) + global APP_DIR + APP_DIR = os.path.dirname(APP_FILE) + global USER_DIR + USER_DIR = userdir() + if USER_DIR is not None: + USER_DIR = os.path.join(USER_DIR,'.asciidoc') + if not os.path.isdir(USER_DIR): + USER_DIR = None + + def load_file(self, fname, dir=None, include=[], exclude=[]): + """ + Loads sections dictionary with sections from file fname. + Existing sections are overlaid. + The 'include' list contains the section names to be loaded. + The 'exclude' list contains section names not to be loaded. + Return False if no file was found in any of the locations. + """ + if dir: + fname = os.path.join(dir, fname) + # Sliently skip missing configuration file. + if not os.path.isfile(fname): + return False + # Don't load conf files twice (local and application conf files are the + # same if the source file is in the application directory). + if os.path.realpath(fname) in self.loaded: + return True + rdr = Reader() # Reader processes system macros. + message.linenos = False # Disable document line numbers. + rdr.open(fname) + message.linenos = None + self.fname = fname + reo = re.compile(r'(?u)^\[(?P<section>[^\W\d][\w-]*)\]\s*$') + sections = OrderedDict() + section,contents = '',[] + while not rdr.eof(): + s = rdr.read() + if s and s[0] == '#': # Skip comment lines. + continue + if s[:2] == '\\#': # Unescape lines starting with '#'. + s = s[1:] + s = s.rstrip() + found = reo.findall(s) + if found: + if section: # Store previous section. + if section in sections \ + and self.entries_section(section): + if ''.join(contents): + # Merge entries. + sections[section] = sections[section] + contents + else: + del sections[section] + else: + sections[section] = contents + section = found[0].lower() + contents = [] + else: + contents.append(s) + if section and contents: # Store last section. + if section in sections \ + and self.entries_section(section): + if ''.join(contents): + # Merge entries. + sections[section] = sections[section] + contents + else: + del sections[section] + else: + sections[section] = contents + rdr.close() + if include: + for s in set(sections) - set(include): + del sections[s] + if exclude: + for s in set(sections) & set(exclude): + del sections[s] + attrs = {} + self.load_sections(sections,attrs) + if not include: + # If all sections are loaded mark this file as loaded. + self.loaded.append(os.path.realpath(fname)) + document.update_attributes(attrs) # So they are available immediately. + return True + + def load_sections(self,sections,attrs=None): + """ + Loads sections dictionary. Each dictionary entry contains a + list of lines. + Updates 'attrs' with parsed [attributes] section entries. + """ + # Delete trailing blank lines from sections. + for k in sections.keys(): + for i in range(len(sections[k])-1,-1,-1): + if not sections[k][i]: + del sections[k][i] + elif not self.entries_section(k): + break + # Add/overwrite new sections. + self.sections.update(sections) + self.parse_tags() + # Internally [miscellaneous] section entries are just attributes. + d = {} + parse_entries(sections.get('miscellaneous',()), d, unquote=True, + allow_name_only=True) + parse_entries(sections.get('attributes',()), d, unquote=True, + allow_name_only=True) + update_attrs(self.conf_attrs,d) + if attrs is not None: + attrs.update(d) + d = {} + parse_entries(sections.get('titles',()),d) + Title.load(d) + parse_entries(sections.get('specialcharacters',()),self.specialchars,escape_delimiter=False) + parse_entries(sections.get('quotes',()),self.quotes) + self.parse_specialwords() + self.parse_replacements() + self.parse_replacements('replacements2') + self.parse_specialsections() + paragraphs.load(sections) + lists.load(sections) + blocks.load(sections) + tables_OLD.load(sections) + tables.load(sections) + macros.load(sections.get('macros',())) + + def get_load_dirs(self): + """ + Return list of well known paths with conf files. + """ + result = [] + if localapp(): + # Load from folders in asciidoc executable directory. + result.append(APP_DIR) + else: + # Load from global configuration directory. + result.append(CONF_DIR) + # Load configuration files from ~/.asciidoc if it exists. + if USER_DIR is not None: + result.append(USER_DIR) + return result + + def find_in_dirs(self, filename, dirs=None): + """ + Find conf files from dirs list. + Return list of found file paths. + Return empty list if not found in any of the locations. + """ + result = [] + if dirs is None: + dirs = self.get_load_dirs() + for d in dirs: + f = os.path.join(d,filename) + if os.path.isfile(f): + result.append(f) + return result + + def load_from_dirs(self, filename, dirs=None, include=[]): + """ + Load conf file from dirs list. + If dirs not specified try all the well known locations. + Return False if no file was sucessfully loaded. + """ + count = 0 + for f in self.find_in_dirs(filename,dirs): + if self.load_file(f, include=include): + count += 1 + return count != 0 + + def load_backend(self, dirs=None): + """ + Load the backend configuration files from dirs list. + If dirs not specified try all the well known locations. + """ + if dirs is None: + dirs = self.get_load_dirs() + for d in dirs: + conf = document.backend + '.conf' + self.load_file(conf,d) + conf = document.backend + '-' + document.doctype + '.conf' + self.load_file(conf,d) + + def load_filters(self, dirs=None): + """ + Load filter configuration files from 'filters' directory in dirs list. + If dirs not specified try all the well known locations. + """ + if dirs is None: + dirs = self.get_load_dirs() + for d in dirs: + # Load filter .conf files. + filtersdir = os.path.join(d,'filters') + for dirpath,dirnames,filenames in os.walk(filtersdir): + for f in filenames: + if re.match(r'^.+\.conf$',f): + self.load_file(f,dirpath) + + def load_miscellaneous(self,d): + """Set miscellaneous configuration entries from dictionary 'd'.""" + def set_misc(name,rule='True',intval=False): + if name in d: + errmsg = 'illegal [miscellaneous] %s entry' % name + if intval: + setattr(self, name, int(validate(d[name],rule,errmsg))) + else: + setattr(self, name, validate(d[name],rule,errmsg)) + set_misc('tabsize','int($)>0',intval=True) + set_misc('textwidth','int($)>0',intval=True) # DEPRECATED: Old tables only. + set_misc('pagewidth','"%f" % $') + if 'pagewidth' in d: + self.pagewidth = float(self.pagewidth) + set_misc('pageunits') + set_misc('outfilesuffix') + if 'newline' in d: + # Convert escape sequences to their character values. + self.newline = eval('"'+d['newline']+'"') + if 'subsnormal' in d: + self.subsnormal = parse_options(d['subsnormal'],SUBS_OPTIONS, + 'illegal [%s] %s: %s' % + ('miscellaneous','subsnormal',d['subsnormal'])) + if 'subsverbatim' in d: + self.subsverbatim = parse_options(d['subsverbatim'],SUBS_OPTIONS, + 'illegal [%s] %s: %s' % + ('miscellaneous','subsverbatim',d['subsverbatim'])) + + def validate(self): + """Check the configuration for internal consistancy. Called after all + configuration files have been loaded.""" + message.linenos = False # Disable document line numbers. + # Heuristic to validate that at least one configuration file was loaded. + if not self.specialchars or not self.tags or not lists: + raise EAsciiDoc,'incomplete configuration files' + # Check special characters are only one character long. + for k in self.specialchars.keys(): + if len(k) != 1: + raise EAsciiDoc,'[specialcharacters] ' \ + 'must be a single character: %s' % k + # Check all special words have a corresponding inline macro body. + for macro in self.specialwords.values(): + if not is_name(macro): + raise EAsciiDoc,'illegal special word name: %s' % macro + if not macro in self.sections: + message.warning('missing special word macro: [%s]' % macro) + # Check all text quotes have a corresponding tag. + for q in self.quotes.keys()[:]: + tag = self.quotes[q] + if not tag: + del self.quotes[q] # Undefine quote. + else: + if tag[0] == '#': + tag = tag[1:] + if not tag in self.tags: + message.warning('[quotes] %s missing tag definition: %s' % (q,tag)) + # Check all specialsections section names exist. + for k,v in self.specialsections.items(): + if not v: + del self.specialsections[k] + elif not v in self.sections: + message.warning('missing specialsections section: [%s]' % v) + paragraphs.validate() + lists.validate() + blocks.validate() + tables_OLD.validate() + tables.validate() + macros.validate() + message.linenos = None + + def entries_section(self,section_name): + """ + Return True if conf file section contains entries, not a markup + template. + """ + for name in self.ENTRIES_SECTIONS: + if re.match(name,section_name): + return True + return False + + def dump(self): + """Dump configuration to stdout.""" + # Header. + hdr = '' + hdr = hdr + '#' + writer.newline + hdr = hdr + '# Generated by AsciiDoc %s for %s %s.%s' % \ + (VERSION,document.backend,document.doctype,writer.newline) + t = time.asctime(time.localtime(time.time())) + hdr = hdr + '# %s%s' % (t,writer.newline) + hdr = hdr + '#' + writer.newline + sys.stdout.write(hdr) + # Dump special sections. + # Dump only the configuration file and command-line attributes. + # [miscellanous] entries are dumped as part of the [attributes]. + d = {} + d.update(self.conf_attrs) + d.update(self.cmd_attrs) + dump_section('attributes',d) + Title.dump() + dump_section('quotes',self.quotes) + dump_section('specialcharacters',self.specialchars) + d = {} + for k,v in self.specialwords.items(): + if v in d: + d[v] = '%s "%s"' % (d[v],k) # Append word list. + else: + d[v] = '"%s"' % k + dump_section('specialwords',d) + dump_section('replacements',self.replacements) + dump_section('replacements2',self.replacements2) + dump_section('specialsections',self.specialsections) + d = {} + for k,v in self.tags.items(): + d[k] = '%s|%s' % v + dump_section('tags',d) + paragraphs.dump() + lists.dump() + blocks.dump() + tables_OLD.dump() + tables.dump() + macros.dump() + # Dump remaining sections. + for k in self.sections.keys(): + if not self.entries_section(k): + sys.stdout.write('[%s]%s' % (k,writer.newline)) + for line in self.sections[k]: + sys.stdout.write('%s%s' % (line,writer.newline)) + sys.stdout.write(writer.newline) + + def subs_section(self,section,d): + """Section attribute substitution using attributes from + document.attributes and 'd'. Lines containing undefinded + attributes are deleted.""" + if section in self.sections: + return subs_attrs(self.sections[section],d) + else: + message.warning('missing section: [%s]' % section) + return () + + def parse_tags(self): + """Parse [tags] section entries into self.tags dictionary.""" + d = {} + parse_entries(self.sections.get('tags',()),d) + for k,v in d.items(): + if v is None: + if k in self.tags: + del self.tags[k] + elif v == '': + self.tags[k] = (None,None) + else: + mo = re.match(r'(?P<stag>.*)\|(?P<etag>.*)',v) + if mo: + self.tags[k] = (mo.group('stag'), mo.group('etag')) + else: + raise EAsciiDoc,'[tag] %s value malformed' % k + + def tag(self, name, d=None): + """Returns (starttag,endtag) tuple named name from configuration file + [tags] section. Raise error if not found. If a dictionary 'd' is + passed then merge with document attributes and perform attribute + substitution on tags.""" + if not name in self.tags: + raise EAsciiDoc, 'missing tag: %s' % name + stag,etag = self.tags[name] + if d is not None: + # TODO: Should we warn if substitution drops a tag? + if stag: + stag = subs_attrs(stag,d) + if etag: + etag = subs_attrs(etag,d) + if stag is None: stag = '' + if etag is None: etag = '' + return (stag,etag) + + def parse_specialsections(self): + """Parse specialsections section to self.specialsections dictionary.""" + # TODO: This is virtually the same as parse_replacements() and should + # be factored to single routine. + d = {} + parse_entries(self.sections.get('specialsections',()),d,unquote=True) + for pat,sectname in d.items(): + pat = strip_quotes(pat) + if not is_re(pat): + raise EAsciiDoc,'[specialsections] entry ' \ + 'is not a valid regular expression: %s' % pat + if sectname is None: + if pat in self.specialsections: + del self.specialsections[pat] + else: + self.specialsections[pat] = sectname + + def parse_replacements(self,sect='replacements'): + """Parse replacements section into self.replacements dictionary.""" + d = OrderedDict() + parse_entries(self.sections.get(sect,()), d, unquote=True) + for pat,rep in d.items(): + if not self.set_replacement(pat, rep, getattr(self,sect)): + raise EAsciiDoc,'[%s] entry in %s is not a valid' \ + ' regular expression: %s' % (sect,self.fname,pat) + + @staticmethod + def set_replacement(pat, rep, replacements): + """Add pattern and replacement to replacements dictionary.""" + pat = strip_quotes(pat) + if not is_re(pat): + return False + if rep is None: + if pat in replacements: + del replacements[pat] + else: + replacements[pat] = strip_quotes(rep) + return True + + def subs_replacements(self,s,sect='replacements'): + """Substitute patterns from self.replacements in 's'.""" + result = s + for pat,rep in getattr(self,sect).items(): + result = re.sub(pat, rep, result) + return result + + def parse_specialwords(self): + """Parse special words section into self.specialwords dictionary.""" + reo = re.compile(r'(?:\s|^)(".+?"|[^"\s]+)(?=\s|$)') + for line in self.sections.get('specialwords',()): + e = parse_entry(line) + if not e: + raise EAsciiDoc,'[specialwords] entry in %s is malformed: %s' \ + % (self.fname,line) + name,wordlist = e + if not is_name(name): + raise EAsciiDoc,'[specialwords] name in %s is illegal: %s' \ + % (self.fname,name) + if wordlist is None: + # Undefine all words associated with 'name'. + for k,v in self.specialwords.items(): + if v == name: + del self.specialwords[k] + else: + words = reo.findall(wordlist) + for word in words: + word = strip_quotes(word) + if not is_re(word): + raise EAsciiDoc,'[specialwords] entry in %s ' \ + 'is not a valid regular expression: %s' \ + % (self.fname,word) + self.specialwords[word] = name + + def subs_specialchars(self,s): + """Perform special character substitution on string 's'.""" + """It may seem like a good idea to escape special characters with a '\' + character, the reason we don't is because the escape character itself + then has to be escaped and this makes including code listings + problematic. Use the predefined {amp},{lt},{gt} attributes instead.""" + result = '' + for ch in s: + result = result + self.specialchars.get(ch,ch) + return result + + def subs_specialchars_reverse(self,s): + """Perform reverse special character substitution on string 's'.""" + result = s + for k,v in self.specialchars.items(): + result = result.replace(v, k) + return result + + def subs_specialwords(self,s): + """Search for word patterns from self.specialwords in 's' and + substitute using corresponding macro.""" + result = s + for word in self.specialwords.keys(): + result = re.sub(word, _subs_specialwords, result) + return result + + def expand_templates(self,entries): + """Expand any template::[] macros in a list of section entries.""" + result = [] + for line in entries: + mo = macros.match('+',r'template',line) + if mo: + s = mo.group('attrlist') + if s in self.sections: + result += self.expand_templates(self.sections[s]) + else: + message.warning('missing section: [%s]' % s) + result.append(line) + else: + result.append(line) + return result + + def expand_all_templates(self): + for k,v in self.sections.items(): + self.sections[k] = self.expand_templates(v) + + def section2tags(self, section, d={}, skipstart=False, skipend=False): + """Perform attribute substitution on 'section' using document + attributes plus 'd' attributes. Return tuple (stag,etag) containing + pre and post | placeholder tags. 'skipstart' and 'skipend' are + used to suppress substitution.""" + assert section is not None + if section in self.sections: + body = self.sections[section] + else: + message.warning('missing section: [%s]' % section) + body = () + # Split macro body into start and end tag lists. + stag = [] + etag = [] + in_stag = True + for s in body: + if in_stag: + mo = re.match(r'(?P<stag>.*)\|(?P<etag>.*)',s) + if mo: + if mo.group('stag'): + stag.append(mo.group('stag')) + if mo.group('etag'): + etag.append(mo.group('etag')) + in_stag = False + else: + stag.append(s) + else: + etag.append(s) + # Do attribute substitution last so {brkbar} can be used to escape |. + # But don't do attribute substitution on title -- we've already done it. + title = d.get('title') + if title: + d['title'] = chr(0) # Replace with unused character. + if not skipstart: + stag = subs_attrs(stag, d) + if not skipend: + etag = subs_attrs(etag, d) + # Put the {title} back. + if title: + stag = map(lambda x: x.replace(chr(0), title), stag) + etag = map(lambda x: x.replace(chr(0), title), etag) + d['title'] = title + return (stag,etag) + + +#--------------------------------------------------------------------------- +# Deprecated old table classes follow. +# Naming convention is an _OLD name suffix. +# These will be removed from future versions of AsciiDoc + +def join_lines_OLD(lines): + """Return a list in which lines terminated with the backslash line + continuation character are joined.""" + result = [] + s = '' + continuation = False + for line in lines: + if line and line[-1] == '\\': + s = s + line[:-1] + continuation = True + continue + if continuation: + result.append(s+line) + s = '' + continuation = False + else: + result.append(line) + if continuation: + result.append(s) + return result + +class Column_OLD: + """Table column.""" + def __init__(self): + self.colalign = None # 'left','right','center' + self.rulerwidth = None + self.colwidth = None # Output width in page units. + +class Table_OLD(AbstractBlock): + COL_STOP = r"(`|'|\.)" # RE. + ALIGNMENTS = {'`':'left', "'":'right', '.':'center'} + FORMATS = ('fixed','csv','dsv') + def __init__(self): + AbstractBlock.__init__(self) + self.CONF_ENTRIES += ('template','fillchar','format','colspec', + 'headrow','footrow','bodyrow','headdata', + 'footdata', 'bodydata') + # Configuration parameters. + self.fillchar=None + self.format=None # 'fixed','csv','dsv' + self.colspec=None + self.headrow=None + self.footrow=None + self.bodyrow=None + self.headdata=None + self.footdata=None + self.bodydata=None + # Calculated parameters. + self.underline=None # RE matching current table underline. + self.isnumeric=False # True if numeric ruler. + self.tablewidth=None # Optional table width scale factor. + self.columns=[] # List of Columns. + # Other. + self.check_msg='' # Message set by previous self.validate() call. + def load(self,name,entries): + AbstractBlock.load(self,name,entries) + """Update table definition from section entries in 'entries'.""" + for k,v in entries.items(): + if k == 'fillchar': + if v and len(v) == 1: + self.fillchar = v + else: + raise EAsciiDoc,'malformed table fillchar: %s' % v + elif k == 'format': + if v in Table_OLD.FORMATS: + self.format = v + else: + raise EAsciiDoc,'illegal table format: %s' % v + elif k == 'colspec': + self.colspec = v + elif k == 'headrow': + self.headrow = v + elif k == 'footrow': + self.footrow = v + elif k == 'bodyrow': + self.bodyrow = v + elif k == 'headdata': + self.headdata = v + elif k == 'footdata': + self.footdata = v + elif k == 'bodydata': + self.bodydata = v + def dump(self): + AbstractBlock.dump(self) + write = lambda s: sys.stdout.write('%s%s' % (s,writer.newline)) + write('fillchar='+self.fillchar) + write('format='+self.format) + if self.colspec: + write('colspec='+self.colspec) + if self.headrow: + write('headrow='+self.headrow) + if self.footrow: + write('footrow='+self.footrow) + write('bodyrow='+self.bodyrow) + if self.headdata: + write('headdata='+self.headdata) + if self.footdata: + write('footdata='+self.footdata) + write('bodydata='+self.bodydata) + write('') + def validate(self): + AbstractBlock.validate(self) + """Check table definition and set self.check_msg if invalid else set + self.check_msg to blank string.""" + # Check global table parameters. + if config.textwidth is None: + self.check_msg = 'missing [miscellaneous] textwidth entry' + elif config.pagewidth is None: + self.check_msg = 'missing [miscellaneous] pagewidth entry' + elif config.pageunits is None: + self.check_msg = 'missing [miscellaneous] pageunits entry' + elif self.headrow is None: + self.check_msg = 'missing headrow entry' + elif self.footrow is None: + self.check_msg = 'missing footrow entry' + elif self.bodyrow is None: + self.check_msg = 'missing bodyrow entry' + elif self.headdata is None: + self.check_msg = 'missing headdata entry' + elif self.footdata is None: + self.check_msg = 'missing footdata entry' + elif self.bodydata is None: + self.check_msg = 'missing bodydata entry' + else: + # No errors. + self.check_msg = '' + def isnext(self): + return AbstractBlock.isnext(self) + def parse_ruler(self,ruler): + """Parse ruler calculating underline and ruler column widths.""" + fc = re.escape(self.fillchar) + # Strip and save optional tablewidth from end of ruler. + mo = re.match(r'^(.*'+fc+r'+)([\d\.]+)$',ruler) + if mo: + ruler = mo.group(1) + self.tablewidth = float(mo.group(2)) + self.attributes['tablewidth'] = str(float(self.tablewidth)) + else: + self.tablewidth = None + self.attributes['tablewidth'] = '100.0' + # Guess whether column widths are specified numerically or not. + if ruler[1] != self.fillchar: + # If the first column does not start with a fillchar then numeric. + self.isnumeric = True + elif ruler[1:] == self.fillchar*len(ruler[1:]): + # The case of one column followed by fillchars is numeric. + self.isnumeric = True + else: + self.isnumeric = False + # Underlines must be 3 or more fillchars. + self.underline = r'^' + fc + r'{3,}$' + splits = re.split(self.COL_STOP,ruler)[1:] + # Build self.columns. + for i in range(0,len(splits),2): + c = Column_OLD() + c.colalign = self.ALIGNMENTS[splits[i]] + s = splits[i+1] + if self.isnumeric: + # Strip trailing fillchars. + s = re.sub(fc+r'+$','',s) + if s == '': + c.rulerwidth = None + else: + c.rulerwidth = int(validate(s,'int($)>0', + 'malformed ruler: bad width')) + else: # Calculate column width from inter-fillchar intervals. + if not re.match(r'^'+fc+r'+$',s): + raise EAsciiDoc,'malformed ruler: illegal fillchars' + c.rulerwidth = len(s)+1 + self.columns.append(c) + # Fill in unspecified ruler widths. + if self.isnumeric: + if self.columns[0].rulerwidth is None: + prevwidth = 1 + for c in self.columns: + if c.rulerwidth is None: + c.rulerwidth = prevwidth + prevwidth = c.rulerwidth + def build_colspecs(self): + """Generate colwidths and colspecs. This can only be done after the + table arguments have been parsed since we use the table format.""" + self.attributes['cols'] = len(self.columns) + # Calculate total ruler width. + totalwidth = 0 + for c in self.columns: + totalwidth = totalwidth + c.rulerwidth + if totalwidth <= 0: + raise EAsciiDoc,'zero width table' + # Calculate marked up colwidths from rulerwidths. + for c in self.columns: + # Convert ruler width to output page width. + width = float(c.rulerwidth) + if self.format == 'fixed': + if self.tablewidth is None: + # Size proportional to ruler width. + colfraction = width/config.textwidth + else: + # Size proportional to page width. + colfraction = width/totalwidth + else: + # Size proportional to page width. + colfraction = width/totalwidth + c.colwidth = colfraction * config.pagewidth # To page units. + if self.tablewidth is not None: + c.colwidth = c.colwidth * self.tablewidth # Scale factor. + if self.tablewidth > 1: + c.colwidth = c.colwidth/100 # tablewidth is in percent. + # Build colspecs. + if self.colspec: + cols = [] + i = 0 + for c in self.columns: + i += 1 + self.attributes['colalign'] = c.colalign + self.attributes['colwidth'] = str(int(c.colwidth)) + self.attributes['colnumber'] = str(i + 1) + s = subs_attrs(self.colspec,self.attributes) + if not s: + message.warning('colspec dropped: contains undefined attribute') + else: + cols.append(s) + self.attributes['colspecs'] = writer.newline.join(cols) + def split_rows(self,rows): + """Return a two item tuple containing a list of lines up to but not + including the next underline (continued lines are joined ) and the + tuple of all lines after the underline.""" + reo = re.compile(self.underline) + i = 0 + while not reo.match(rows[i]): + i = i+1 + if i == 0: + raise EAsciiDoc,'missing table rows' + if i >= len(rows): + raise EAsciiDoc,'closing [%s] underline expected' % self.name + return (join_lines_OLD(rows[:i]), rows[i+1:]) + def parse_rows(self, rows, rtag, dtag): + """Parse rows list using the row and data tags. Returns a substituted + list of output lines.""" + result = [] + # Source rows are parsed as single block, rather than line by line, to + # allow the CSV reader to handle multi-line rows. + if self.format == 'fixed': + rows = self.parse_fixed(rows) + elif self.format == 'csv': + rows = self.parse_csv(rows) + elif self.format == 'dsv': + rows = self.parse_dsv(rows) + else: + assert True,'illegal table format' + # Substitute and indent all data in all rows. + stag,etag = subs_tag(rtag,self.attributes) + for row in rows: + result.append(' '+stag) + for data in self.subs_row(row,dtag): + result.append(' '+data) + result.append(' '+etag) + return result + def subs_row(self, data, dtag): + """Substitute the list of source row data elements using the data tag. + Returns a substituted list of output table data items.""" + result = [] + if len(data) < len(self.columns): + message.warning('fewer row data items then table columns') + if len(data) > len(self.columns): + message.warning('more row data items than table columns') + for i in range(len(self.columns)): + if i > len(data) - 1: + d = '' # Fill missing column data with blanks. + else: + d = data[i] + c = self.columns[i] + self.attributes['colalign'] = c.colalign + self.attributes['colwidth'] = str(int(c.colwidth)) + self.attributes['colnumber'] = str(i + 1) + stag,etag = subs_tag(dtag,self.attributes) + # Insert AsciiDoc line break (' +') where row data has newlines + # ('\n'). This is really only useful when the table format is csv + # and the output markup is HTML. It's also a bit dubious in that it + # assumes the user has not modified the shipped line break pattern. + subs = self.get_subs()[0] + if 'replacements' in subs: + # Insert line breaks in cell data. + d = re.sub(r'(?m)\n',r' +\n',d) + d = d.split('\n') # So writer.newline is written. + else: + d = [d] + result = result + [stag] + Lex.subs(d,subs) + [etag] + return result + def parse_fixed(self,rows): + """Parse the list of source table rows. Each row item in the returned + list contains a list of cell data elements.""" + result = [] + for row in rows: + data = [] + start = 0 + # build an encoded representation + row = char_decode(row) + for c in self.columns: + end = start + c.rulerwidth + if c is self.columns[-1]: + # Text in last column can continue forever. + # Use the encoded string to slice, but convert back + # to plain string before further processing + data.append(char_encode(row[start:]).strip()) + else: + data.append(char_encode(row[start:end]).strip()) + start = end + result.append(data) + return result + def parse_csv(self,rows): + """Parse the list of source table rows. Each row item in the returned + list contains a list of cell data elements.""" + import StringIO + import csv + result = [] + rdr = csv.reader(StringIO.StringIO('\r\n'.join(rows)), + skipinitialspace=True) + try: + for row in rdr: + result.append(row) + except Exception: + raise EAsciiDoc,'csv parse error: %s' % row + return result + def parse_dsv(self,rows): + """Parse the list of source table rows. Each row item in the returned + list contains a list of cell data elements.""" + separator = self.attributes.get('separator',':') + separator = eval('"'+separator+'"') + if len(separator) != 1: + raise EAsciiDoc,'malformed dsv separator: %s' % separator + # TODO If separator is preceeded by an odd number of backslashes then + # it is escaped and should not delimit. + result = [] + for row in rows: + # Skip blank lines + if row == '': continue + # Unescape escaped characters. + row = eval('"'+row.replace('"','\\"')+'"') + data = row.split(separator) + data = [s.strip() for s in data] + result.append(data) + return result + def translate(self): + message.deprecated('old tables syntax') + AbstractBlock.translate(self) + # Reset instance specific properties. + self.underline = None + self.columns = [] + attrs = {} + BlockTitle.consume(attrs) + # Add relevant globals to table substitutions. + attrs['pagewidth'] = str(config.pagewidth) + attrs['pageunits'] = config.pageunits + # Mix in document attribute list. + AttributeList.consume(attrs) + # Validate overridable attributes. + for k,v in attrs.items(): + if k == 'format': + if v not in self.FORMATS: + raise EAsciiDoc, 'illegal [%s] %s: %s' % (self.name,k,v) + self.format = v + elif k == 'tablewidth': + try: + self.tablewidth = float(attrs['tablewidth']) + except Exception: + raise EAsciiDoc, 'illegal [%s] %s: %s' % (self.name,k,v) + self.merge_attributes(attrs) + # Parse table ruler. + ruler = reader.read() + assert re.match(self.delimiter,ruler) + self.parse_ruler(ruler) + # Read the entire table. + table = [] + while True: + line = reader.read_next() + # Table terminated by underline followed by a blank line or EOF. + if len(table) > 0 and re.match(self.underline,table[-1]): + if line in ('',None): + break; + if line is None: + raise EAsciiDoc,'closing [%s] underline expected' % self.name + table.append(reader.read()) + # EXPERIMENTAL: The number of lines in the table, requested by Benjamin Klum. + self.attributes['rows'] = str(len(table)) + if self.check_msg: # Skip if table definition was marked invalid. + message.warning('skipping %s table: %s' % (self.name,self.check_msg)) + return + # Generate colwidths and colspecs. + self.build_colspecs() + # Generate headrows, footrows, bodyrows. + # Headrow, footrow and bodyrow data replaces same named attributes in + # the table markup template. In order to ensure this data does not get + # a second attribute substitution (which would interfere with any + # already substituted inline passthroughs) unique placeholders are used + # (the tab character does not appear elsewhere since it is expanded on + # input) which are replaced after template attribute substitution. + headrows = footrows = [] + bodyrows,table = self.split_rows(table) + if table: + headrows = bodyrows + bodyrows,table = self.split_rows(table) + if table: + footrows,table = self.split_rows(table) + if headrows: + headrows = self.parse_rows(headrows, self.headrow, self.headdata) + headrows = writer.newline.join(headrows) + self.attributes['headrows'] = '\x07headrows\x07' + if footrows: + footrows = self.parse_rows(footrows, self.footrow, self.footdata) + footrows = writer.newline.join(footrows) + self.attributes['footrows'] = '\x07footrows\x07' + bodyrows = self.parse_rows(bodyrows, self.bodyrow, self.bodydata) + bodyrows = writer.newline.join(bodyrows) + self.attributes['bodyrows'] = '\x07bodyrows\x07' + table = subs_attrs(config.sections[self.template],self.attributes) + table = writer.newline.join(table) + # Before we finish replace the table head, foot and body place holders + # with the real data. + if headrows: + table = table.replace('\x07headrows\x07', headrows, 1) + if footrows: + table = table.replace('\x07footrows\x07', footrows, 1) + table = table.replace('\x07bodyrows\x07', bodyrows, 1) + writer.write(table,trace='table') + +class Tables_OLD(AbstractBlocks): + """List of tables.""" + BLOCK_TYPE = Table_OLD + PREFIX = 'old_tabledef-' + def __init__(self): + AbstractBlocks.__init__(self) + def load(self,sections): + AbstractBlocks.load(self,sections) + def validate(self): + # Does not call AbstractBlocks.validate(). + # Check we have a default table definition, + for i in range(len(self.blocks)): + if self.blocks[i].name == 'old_tabledef-default': + default = self.blocks[i] + break + else: + raise EAsciiDoc,'missing section: [OLD_tabledef-default]' + # Set default table defaults. + if default.format is None: default.subs = 'fixed' + # Propagate defaults to unspecified table parameters. + for b in self.blocks: + if b is not default: + if b.fillchar is None: b.fillchar = default.fillchar + if b.format is None: b.format = default.format + if b.template is None: b.template = default.template + if b.colspec is None: b.colspec = default.colspec + if b.headrow is None: b.headrow = default.headrow + if b.footrow is None: b.footrow = default.footrow + if b.bodyrow is None: b.bodyrow = default.bodyrow + if b.headdata is None: b.headdata = default.headdata + if b.footdata is None: b.footdata = default.footdata + if b.bodydata is None: b.bodydata = default.bodydata + # Check all tables have valid fill character. + for b in self.blocks: + if not b.fillchar or len(b.fillchar) != 1: + raise EAsciiDoc,'[%s] missing or illegal fillchar' % b.name + # Build combined tables delimiter patterns and assign defaults. + delimiters = [] + for b in self.blocks: + # Ruler is: + # (ColStop,(ColWidth,FillChar+)?)+, FillChar+, TableWidth? + b.delimiter = r'^(' + Table_OLD.COL_STOP \ + + r'(\d*|' + re.escape(b.fillchar) + r'*)' \ + + r')+' \ + + re.escape(b.fillchar) + r'+' \ + + '([\d\.]*)$' + delimiters.append(b.delimiter) + if not b.headrow: + b.headrow = b.bodyrow + if not b.footrow: + b.footrow = b.bodyrow + if not b.headdata: + b.headdata = b.bodydata + if not b.footdata: + b.footdata = b.bodydata + self.delimiters = re_join(delimiters) + # Check table definitions are valid. + for b in self.blocks: + b.validate() + if config.verbose: + if b.check_msg: + message.warning('[%s] table definition: %s' % (b.name,b.check_msg)) + +# End of deprecated old table classes. +#--------------------------------------------------------------------------- + +#--------------------------------------------------------------------------- +# Filter commands. +#--------------------------------------------------------------------------- +import shutil, zipfile + +def die(msg): + message.stderr(msg) + sys.exit(1) + +def unzip(zip_file, destdir): + """ + Unzip Zip file to destination directory. + Throws exception if error occurs. + """ + zipo = zipfile.ZipFile(zip_file, 'r') + try: + for zi in zipo.infolist(): + outfile = zi.filename + if not outfile.endswith('/'): + d, outfile = os.path.split(outfile) + directory = os.path.normpath(os.path.join(destdir, d)) + if not os.path.isdir(directory): + os.makedirs(directory) + outfile = os.path.join(directory, outfile) + perms = (zi.external_attr >> 16) & 0777 + message.verbose('extracting: %s' % outfile) + fh = os.open(outfile, os.O_CREAT | os.O_WRONLY, perms) + try: + os.write(fh, zipo.read(zi.filename)) + finally: + os.close(fh) + finally: + zipo.close() + +class Filter: + """ + --filter option commands. + """ + + @staticmethod + def get_filters_dir(): + """ + Return path of .asciidoc/filters in user's home direcory or None if + user home not defined. + """ + result = userdir() + if result: + result = os.path.join(result,'.asciidoc','filters') + return result + + @staticmethod + def install(args): + """ + Install filter Zip file. + args[0] is filter zip file path. + args[1] is optional destination filters directory. + """ + if len(args) not in (1,2): + die('invalid number of arguments: --filter install %s' + % ' '.join(args)) + zip_file = args[0] + if not os.path.isfile(zip_file): + die('file not found: %s' % zip_file) + reo = re.match(r'^\w+',os.path.split(zip_file)[1]) + if not reo: + die('filter file name does not start with legal filter name: %s' + % zip_file) + filter_name = reo.group() + if len(args) == 2: + filters_dir = args[1] + if not os.path.isdir(filters_dir): + die('directory not found: %s' % filters_dir) + else: + filters_dir = Filter.get_filters_dir() + if not filters_dir: + die('user home directory is not defined') + filter_dir = os.path.join(filters_dir, filter_name) + if os.path.exists(filter_dir): + die('filter is already installed: %s' % filter_dir) + try: + os.makedirs(filter_dir) + except Exception,e: + die('failed to create filter directory: %s' % str(e)) + try: + unzip(zip_file, filter_dir) + except Exception,e: + die('failed to extract filter: %s' % str(e)) + + @staticmethod + def remove(args): + """ + Delete filter from .asciidoc/filters/ in user's home directory. + args[0] is filter name. + args[1] is optional filters directory. + """ + if len(args) not in (1,2): + die('invalid number of arguments: --filter remove %s' + % ' '.join(args)) + filter_name = args[0] + if not re.match(r'^\w+$',filter_name): + die('illegal filter name: %s' % filter_name) + if len(args) == 2: + d = args[1] + if not os.path.isdir(d): + die('directory not found: %s' % d) + else: + d = Filter.get_filters_dir() + if not d: + die('user directory is not defined') + filter_dir = os.path.join(d, filter_name) + if not os.path.isdir(filter_dir): + die('cannot find filter: %s' % filter_dir) + try: + message.verbose('removing: %s' % filter_dir) + shutil.rmtree(filter_dir) + except Exception,e: + die('failed to delete filter: %s' % str(e)) + + @staticmethod + def list(): + """ + List all filter directories (global and local). + """ + for d in [os.path.join(d,'filters') for d in config.get_load_dirs()]: + if os.path.isdir(d): + for f in os.walk(d).next()[1]: + message.stdout(os.path.join(d,f)) + + +#--------------------------------------------------------------------------- +# Application code. +#--------------------------------------------------------------------------- +# Constants +# --------- +APP_FILE = None # This file's full path. +APP_DIR = None # This file's directory. +USER_DIR = None # ~/.asciidoc +# Global configuration files directory (set by Makefile build target). +CONF_DIR = '/etc/asciidoc' +HELP_FILE = 'help.conf' # Default (English) help file. + +# Globals +# ------- +document = Document() # The document being processed. +config = Config() # Configuration file reader. +reader = Reader() # Input stream line reader. +writer = Writer() # Output stream line writer. +message = Message() # Message functions. +paragraphs = Paragraphs() # Paragraph definitions. +lists = Lists() # List definitions. +blocks = DelimitedBlocks() # DelimitedBlock definitions. +tables_OLD = Tables_OLD() # Table_OLD definitions. +tables = Tables() # Table definitions. +macros = Macros() # Macro definitions. +calloutmap = CalloutMap() # Coordinates callouts and callout list. +trace = Trace() # Implements trace attribute processing. + +### Used by asciidocapi.py ### +# List of message strings written to stderr. +messages = message.messages + + +def asciidoc(backend, doctype, confiles, infile, outfile, options): + """Convert AsciiDoc document to DocBook document of type doctype + The AsciiDoc document is read from file object src the translated + DocBook file written to file object dst.""" + def load_conffiles(include=[], exclude=[]): + # Load conf files specified on the command-line and by the conf-files attribute. + files = document.attributes.get('conf-files','') + files = [f.strip() for f in files.split('|') if f.strip()] + files += confiles + if files: + for f in files: + if os.path.isfile(f): + config.load_file(f, include=include, exclude=exclude) + else: + raise EAsciiDoc,'configuration file %s missing' % f + + try: + if doctype not in (None,'article','manpage','book'): + raise EAsciiDoc,'illegal document type' + # Set processing options. + for o in options: + if o == '-c': config.dumping = True + if o == '-s': config.header_footer = False + if o == '-v': config.verbose = True + document.update_attributes() + if '-e' not in options: + # Load asciidoc.conf files in two passes: the first for attributes + # the second for everything. This is so that locally set attributes + # available are in the global asciidoc.conf + if not config.load_from_dirs('asciidoc.conf',include=['attributes']): + raise EAsciiDoc,'configuration file asciidoc.conf missing' + load_conffiles(include=['attributes']) + config.load_from_dirs('asciidoc.conf') + if infile != '<stdin>': + indir = os.path.dirname(infile) + config.load_file('asciidoc.conf', indir, + include=['attributes','titles','specialchars']) + else: + load_conffiles(include=['attributes','titles','specialchars']) + document.update_attributes() + # Check the infile exists. + if infile != '<stdin>': + if not os.path.isfile(infile): + raise EAsciiDoc,'input file %s missing' % infile + document.infile = infile + AttributeList.initialize() + # Open input file and parse document header. + reader.tabsize = config.tabsize + reader.open(infile) + has_header = document.parse_header(doctype,backend) + # doctype is now finalized. + document.attributes['doctype-'+document.doctype] = '' + # Load backend configuration files. + if '-e' not in options: + f = document.backend + '.conf' + if not config.find_in_dirs(f): + message.warning('missing backend conf file: %s' % f, linenos=False) + config.load_backend() + # backend is now known. + document.attributes['backend-'+document.backend] = '' + document.attributes[document.backend+'-'+document.doctype] = '' + doc_conffiles = [] + if '-e' not in options: + # Load filters and language file. + config.load_filters() + document.load_lang() + if infile != '<stdin>': + # Load local conf files (files in the source file directory). + config.load_file('asciidoc.conf', indir) + config.load_backend([indir]) + config.load_filters([indir]) + # Load document specific configuration files. + f = os.path.splitext(infile)[0] + doc_conffiles = [ + f for f in (f+'.conf', f+'-'+document.backend+'.conf') + if os.path.isfile(f) ] + for f in doc_conffiles: + config.load_file(f) + load_conffiles() + # Build asciidoc-args attribute. + args = '' + # Add custom conf file arguments. + for f in doc_conffiles + confiles: + args += ' --conf-file "%s"' % f + # Add command-line and header attributes. + attrs = {} + attrs.update(AttributeEntry.attributes) + attrs.update(config.cmd_attrs) + if 'title' in attrs: # Don't pass the header title. + del attrs['title'] + for k,v in attrs.items(): + if v: + args += ' --attribute "%s=%s"' % (k,v) + else: + args += ' --attribute "%s"' % k + document.attributes['asciidoc-args'] = args + # Build outfile name. + if outfile is None: + outfile = os.path.splitext(infile)[0] + '.' + document.backend + if config.outfilesuffix: + # Change file extension. + outfile = os.path.splitext(outfile)[0] + config.outfilesuffix + document.outfile = outfile + # Document header attributes override conf file attributes. + document.attributes.update(AttributeEntry.attributes) + document.update_attributes() + # Configuration is fully loaded so can expand templates. + config.expand_all_templates() + # Check configuration for consistency. + config.validate() + paragraphs.initialize() + lists.initialize() + if config.dumping: + config.dump() + else: + writer.newline = config.newline + try: + writer.open(outfile, reader.bom) + try: + document.translate(has_header) # Generate the output. + finally: + writer.close() + finally: + reader.closefile() + except KeyboardInterrupt: + raise + except Exception,e: + # Cleanup. + if outfile and outfile != '<stdout>' and os.path.isfile(outfile): + os.unlink(outfile) + # Build and print error description. + msg = 'FAILED: ' + if reader.cursor: + msg = message.format('', msg) + if isinstance(e, EAsciiDoc): + message.stderr('%s%s' % (msg,str(e))) + else: + if __name__ == '__main__': + message.stderr(msg+'unexpected error:') + message.stderr('-'*60) + traceback.print_exc(file=sys.stderr) + message.stderr('-'*60) + else: + message.stderr('%sunexpected error: %s' % (msg,str(e))) + sys.exit(1) + +def usage(msg=''): + if msg: + message.stderr(msg) + show_help('default', sys.stderr) + +def show_help(topic, f=None): + """Print help topic to file object f.""" + if f is None: + f = sys.stdout + # Select help file. + lang = config.cmd_attrs.get('lang') + if lang and lang != 'en': + help_file = 'help-' + lang + '.conf' + else: + help_file = HELP_FILE + # Print [topic] section from help file. + config.load_from_dirs(help_file) + if len(config.sections) == 0: + # Default to English if specified language help files not found. + help_file = HELP_FILE + config.load_from_dirs(help_file) + if len(config.sections) == 0: + message.stderr('no help topics found') + sys.exit(1) + n = 0 + for k in config.sections: + if re.match(re.escape(topic), k): + n += 1 + lines = config.sections[k] + if n == 0: + if topic != 'topics': + message.stderr('help topic not found: [%s] in %s' % (topic, help_file)) + message.stderr('available help topics: %s' % ', '.join(config.sections.keys())) + sys.exit(1) + elif n > 1: + message.stderr('ambiguous help topic: %s' % topic) + else: + for line in lines: + print >>f, line + +### Used by asciidocapi.py ### +def execute(cmd,opts,args): + """ + Execute asciidoc with command-line options and arguments. + cmd is asciidoc command or asciidoc.py path. + opts and args conform to values returned by getopt.getopt(). + Raises SystemExit if an error occurs. + + Doctests: + + 1. Check execution: + + >>> import StringIO + >>> infile = StringIO.StringIO('Hello *{author}*') + >>> outfile = StringIO.StringIO() + >>> opts = [] + >>> opts.append(('--backend','html4')) + >>> opts.append(('--no-header-footer',None)) + >>> opts.append(('--attribute','author=Joe Bloggs')) + >>> opts.append(('--out-file',outfile)) + >>> execute(__file__, opts, [infile]) + >>> print outfile.getvalue() + <p>Hello <strong>Joe Bloggs</strong></p> + + >>> + + """ + config.init(cmd) + if len(args) > 1: + usage('To many arguments') + sys.exit(1) + backend = None + doctype = None + confiles = [] + outfile = None + options = [] + help_option = False + for o,v in opts: + if o in ('--help','-h'): + help_option = True + #DEPRECATED: --unsafe option. + if o == '--unsafe': + document.safe = False + if o == '--safe': + document.safe = True + if o == '--version': + print('asciidoc %s' % VERSION) + sys.exit(0) + if o in ('-b','--backend'): + backend = v +# config.cmd_attrs['backend'] = v + if o in ('-c','--dump-conf'): + options.append('-c') + if o in ('-d','--doctype'): + doctype = v +# config.cmd_attrs['doctype'] = v + if o in ('-e','--no-conf'): + options.append('-e') + if o in ('-f','--conf-file'): + confiles.append(v) + if o in ('-n','--section-numbers'): + o = '-a' + v = 'numbered' + if o in ('-a','--attribute'): + e = parse_entry(v, allow_name_only=True) + if not e: + usage('Illegal -a option: %s' % v) + sys.exit(1) + k,v = e + # A @ suffix denotes don't override existing document attributes. + if v and v[-1] == '@': + document.attributes[k] = v[:-1] + else: + config.cmd_attrs[k] = v + if o in ('-o','--out-file'): + outfile = v + if o in ('-s','--no-header-footer'): + options.append('-s') + if o in ('-v','--verbose'): + options.append('-v') + if help_option: + if len(args) == 0: + show_help('default') + else: + show_help(args[-1]) + sys.exit(0) + if len(args) == 0 and len(opts) == 0: + usage() + sys.exit(0) + if len(args) == 0: + usage('No source file specified') + sys.exit(1) +# if not backend: +# usage('No --backend option specified') +# sys.exit(1) + stdin,stdout = sys.stdin,sys.stdout + try: + infile = args[0] + if infile == '-': + infile = '<stdin>' + elif isinstance(infile, str): + infile = os.path.abspath(infile) + else: # Input file is file object from API call. + sys.stdin = infile + infile = '<stdin>' + if outfile == '-': + outfile = '<stdout>' + elif isinstance(outfile, str): + outfile = os.path.abspath(outfile) + elif outfile is None: + if infile == '<stdin>': + outfile = '<stdout>' + else: # Output file is file object from API call. + sys.stdout = outfile + outfile = '<stdout>' + # Do the work. + asciidoc(backend, doctype, confiles, infile, outfile, options) + if document.has_errors: + sys.exit(1) + finally: + sys.stdin,sys.stdout = stdin,stdout + +if __name__ == '__main__': + # Process command line options. + import getopt + try: + #DEPRECATED: --unsafe option. + opts,args = getopt.getopt(sys.argv[1:], + 'a:b:cd:ef:hno:svw:', + ['attribute=','backend=','conf-file=','doctype=','dump-conf', + 'help','no-conf','no-header-footer','out-file=', + 'section-numbers','verbose','version','safe','unsafe', + 'doctest','filter']) + except getopt.GetoptError: + message.stderr('illegal command options') + sys.exit(1) + if '--doctest' in [opt[0] for opt in opts]: + # Run module doctests. + import doctest + options = doctest.NORMALIZE_WHITESPACE + doctest.ELLIPSIS + failures,tries = doctest.testmod(optionflags=options) + if failures == 0: + message.stderr('All doctests passed') + sys.exit(0) + else: + sys.exit(1) + if '--filter' in [opt[0] for opt in opts]: + config.init(sys.argv[0]) + config.verbose = bool(set(['-v','--verbose']) & set([opt[0] for opt in opts])) + if not args: + die('missing --filter command') + elif args[0] == 'install': + Filter.install(args[1:]) + elif args[0] == 'remove': + Filter.remove(args[1:]) + elif args[0] == 'list': + Filter.list() + else: + die('illegal --filter command: %s' % args[0]) + sys.exit(0) + try: + execute(sys.argv[0],opts,args) + except KeyboardInterrupt: + sys.exit(1) diff --git a/doc/www/create-relnotes.sh b/doc/www/create-relnotes.sh new file mode 100755 index 00000000..9e731e87 --- /dev/null +++ b/doc/www/create-relnotes.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# +# This script requires asciidoc http://www.methods.co.nz/asciidoc/ + +# (echo -e "Netatalk NEWS\n-------------\n\n" ; cat NEWS) | ./asciidoc.py -f netatalk-news.conf -b css -o NEWS.html - && chmod g+w NEWS.html +./asciidoc.py -f netatalk-relnotes.conf -b html5 -o ReleaseNotes.html ReleaseNotes && chmod g+w ReleaseNotes.html diff --git a/doc/www/html5.conf b/doc/www/html5.conf new file mode 100644 index 00000000..cedc3fda --- /dev/null +++ b/doc/www/html5.conf @@ -0,0 +1,686 @@ +# +# html5.conf +# +# Asciidoc configuration file. +# html5 backend. +# + +[miscellaneous] +outfilesuffix=.html + +[attributes] +basebackend=html +basebackend-html= +basebackend-html5= + +[replacements2] +# Line break. +(?m)^(.*)\s\+$=\1<br> + +[replacements] +ifdef::asciidoc7compatible[] +# Superscripts. +\^(.+?)\^=<sup>\1</sup> +# Subscripts. +~(.+?)~=<sub>\1</sub> +endif::asciidoc7compatible[] + +[ruler-blockmacro] +<hr> + +[pagebreak-blockmacro] +<div style="page-break-after:always"></div> + +[blockdef-pass] +asciimath-style=template="asciimathblock",subs=[] +latexmath-style=template="latexmathblock",subs=[] + +[macros] +(?u)^(?P<name>audio|video)::(?P<target>\S*?)(\[(?P<attrlist>.*?)\])$=# +# math macros. +# Special characters are escaped in HTML math markup. +(?su)[\\]?(?P<name>asciimath|latexmath):(?P<subslist>\S*?)\[(?P<passtext>.*?)(?<!\\)\]=[specialcharacters] +(?u)^(?P<name>asciimath|latexmath)::(?P<subslist>\S*?)(\[(?P<passtext>.*?)\])$=#[specialcharacters] + +[asciimath-inlinemacro] +`{passtext}` + +[asciimath-blockmacro] +<div class="mathblock{role? {role}}"{id? id="{id}"}> +<div class="content"> +<div class="title">{title}</div> +`{passtext}` +</div></div> + +[asciimathblock] +<div class="mathblock{role? {role}}"{id? id="{id}"}> +<div class="content"> +<div class="title">{title}</div> +`|` +</div></div> + +[latexmath-inlinemacro] +{passtext} + +[latexmath-blockmacro] +<div class="mathblock{role? {role}}"{id? id="{id}"}> +<div class="content"> +<div class="title">{title}</div> +{passtext} +</div></div> + +[latexmathblock] +<div class="mathblock{role? {role}}"{id? id="{id}"}> +<div class="content"> +<div class="title">{title}</div> +| +</div></div> + +[image-inlinemacro] +<span class="image{role? {role}}"> +<a class="image" href="{link}"> +{data-uri%}<img src="{imagesdir=}{imagesdir?/}{target}" alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"}{title? title="{title}"}> +{data-uri#}<img alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"}{title? title="{title}"} src="data:image/{eval:os.path.splitext('{target}')[1][1:]};base64, +{data-uri#}{sys3:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{imagesdir=}","{target}")}"}"> +{link#}</a> +</span> + +[image-blockmacro] +<div class="imageblock{style? {style}}{role? {role}}"{id? id="{id}"}{align? style="text-align:{align};"}{float? style="float:{float};"}> +<div class="content"> +<a class="image" href="{link}"> +{data-uri%}<img src="{imagesdir=}{imagesdir?/}{target}" alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"}> +{data-uri#}<img alt="{alt={target}}"{width? width="{width}"}{height? height="{height}"} src="data:image/{eval:os.path.splitext('{target}')[1][1:]};base64, +{data-uri#}{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{imagesdir=}","{target}")}"}"> +{link#}</a> +</div> +<div class="title">{caption={figure-caption} {counter:figure-number}. }{title}</div> +</div> + +[audio-blockmacro] +<div class="audioblock{role? {role}}"{id? id="{id}"}> +<div class="title">{caption=}{title}</div> +<div class="content"> +<audio src="{imagesdir=}{imagesdir?/}{target}"{autoplay-option? autoplay}{nocontrols-option! controls}{loop-option? loop}> +Your browser does not support the audio tag. +</audio> +</div></div> + +[video-blockmacro] +<div class="videoblock{role? {role}}"{id? id="{id}"}> +<div class="title">{caption=}{title}</div> +<div class="content"> +<video src="{imagesdir=}{imagesdir?/}{target}"{width? width="{width}"}{height? height="{height}"}{poster? poster="{poster}"}{autoplay-option? autoplay}{nocontrols-option! controls}{loop-option? loop}> +Your browser does not support the video tag. +</video> +</div></div> + +[unfloat-blockmacro] +<div style="clear:both;"></div> + +[indexterm-inlinemacro] +# Index term. +{empty} + +[indexterm2-inlinemacro] +# Index term. +# Single entry index term that is visible in the primary text flow. +{1} + +[footnote-inlinemacro] +# footnote:[<text>]. +<span class="footnote"><br>[{0}]<br></span> + +[footnoteref-inlinemacro] +# footnoteref:[<id>], create reference to footnote. +{2%}<span class="footnoteref"><br><a href="#_footnote_{1}">[{1}]</a><br></span> +# footnoteref:[<id>,<text>], create footnote with ID. +{2#}<span class="footnote" id="_footnote_{1}"><br>[{2}]<br></span> + +[callout-inlinemacro] +ifndef::icons[] +<b><{index}></b> +endif::icons[] +ifdef::icons[] +ifndef::data-uri[] +<img src="{icon={iconsdir}/callouts/{index}.png}" alt="{index}"> +endif::data-uri[] +ifdef::data-uri[] +<img alt="{index}" src="data:image/png;base64, +{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/callouts/{index}.png}")}"}"> +endif::data-uri[] +endif::icons[] + +# Comment line macros. +[comment-inlinemacro] +{showcomments#}<br><span class="comment">{passtext}</span><br> + +[comment-blockmacro] +{showcomments#}<p><span class="comment">{passtext}</span></p> + +[literal-inlinemacro] +# Inline literal. +<span class="monospaced">{passtext}</span> + +# List tags. +[listtags-bulleted] +list=<div class="ulist{style? {style}}{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ul>|</ul></div> +item=<li>|</li> +text=<p>|</p> + +[listtags-numbered] +# The start attribute is not valid XHTML 1.1 but all browsers support it. +list=<div class="olist{style? {style}}{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol class="{style}"{start? start="{start}"}>|</ol></div> +item=<li>|</li> +text=<p>|</p> + +[listtags-labeled] +list=<div class="dlist{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<dl>|</dl></div> +entry= +label= +term=<dt class="hdlist1{strong-option? strong}">|</dt> +item=<dd>|</dd> +text=<p>|</p> + +[listtags-horizontal] +list=<div class="hdlist{compact-option? compact}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<table>{labelwidth?<col width="{labelwidth}%">}{itemwidth?<col width="{itemwidth}%">}|</table></div> +label=<td class="hdlist1{strong-option? strong}">|</td> +term=|<br> +entry=<tr>|</tr> +item=<td class="hdlist2">|</td> +text=<p style="margin-top: 0;">|</p> + +[listtags-qanda] +list=<div class="qlist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol>|</ol></div> +entry=<li>|</li> +label= +term=<p><em>|</em></p> +item= +text=<p>|</p> + +[listtags-callout] +ifndef::icons[] +list=<div class="colist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ol>|</ol></div> +item=<li>|</li> +text=<p>|</p> +endif::icons[] +ifdef::icons[] +list=<div class="colist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<table>|</table></div> +ifndef::data-uri[] +item=<tr><td><img src="{iconsdir}/callouts/{listindex}.png" alt="{listindex}"></td><td>|</td></tr> +endif::data-uri[] +ifdef::data-uri[] +item=<tr><td><img alt="{listindex}" src="data:image/png;base64, {sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/callouts/{listindex}.png}")}"}"></td><td>|</td></tr> +endif::data-uri[] +text=| +endif::icons[] + +[listtags-glossary] +list=<div class="dlist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<dl>|</dl></div> +label= +entry= +term=<dt>|</dt> +item=<dd>|</dd> +text=<p>|</p> + +[listtags-bibliography] +list=<div class="ulist{style? {style}}{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<ul>|</ul></div> +item=<li>|</li> +text=<p>|</p> + +[tags] +# Quoted text. +emphasis=<em>{1?<span class="{1}">}|{1?</span>}</em> +strong=<strong>{1?<span class="{1}">}|{1?</span>}</strong> +monospaced=<span class="monospaced{1? {1}}">|</span> +singlequoted={lsquo}{1?<span class="{1}">}|{1?</span>}{rsquo} +doublequoted={ldquo}{1?<span class="{1}">}|{1?</span>}{rdquo} +unquoted={1?<span class="{1}">}|{1?</span>} +superscript=<sup>{1?<span class="{1}">}|{1?</span>}</sup> +subscript=<sub>{1?<span class="{1}">}|{1?</span>}</sub> + +ifdef::deprecated-quotes[] +# Override with deprecated quote attributes. +emphasis={role?<span class="{role}">}<em{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</em>{role?</span>} +strong={role?<span class="{role}">}<strong{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</strong>{role?</span>} +monospaced=<span class="monospaced{role? {role}}"{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</span> +singlequoted={role?<span class="{role}">}{1,2,3?<span style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?">}{amp}#8216;|{amp}#8217;{1,2,3?</span>}{role?</span>} +doublequoted={role?<span class="{role}">}{1,2,3?<span style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?">}{amp}#8220;|{amp}#8221;{1,2,3?</span>}{role?</span>} +unquoted={role?<span class="{role}">}{1,2,3?<span style="{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}">}|{1,2,3?</span>}{role?</span>} +superscript={role?<span class="{role}">}<sup{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</sup>{role?</span>} +subscript={role?<span class="{role}">}<sub{1,2,3? style="}{1?color:{1};}{2?background-color:{2};}{3?font-size:{3}em;}{1,2,3?"}>|</sub>{role?</span>} +endif::deprecated-quotes[] + +# Inline macros +[http-inlinemacro] +<a href="{name}:{target}">{0={name}:{target}}</a> +[https-inlinemacro] +<a href="{name}:{target}">{0={name}:{target}}</a> +[ftp-inlinemacro] +<a href="{name}:{target}">{0={name}:{target}}</a> +[file-inlinemacro] +<a href="{name}:{target}">{0={name}:{target}}</a> +[irc-inlinemacro] +<a href="{name}:{target}">{0={name}:{target}}</a> +[mailto-inlinemacro] +<a href="mailto:{target}">{0={target}}</a> +[link-inlinemacro] +<a href="{target}">{0={target}}</a> +[callto-inlinemacro] +<a href="{name}:{target}">{0={target}}</a> +# anchor:id[text] +[anchor-inlinemacro] +<a id="{target}"></a> +# [[id,text]] +[anchor2-inlinemacro] +<a id="{1}"></a> +# [[[id]]] +[anchor3-inlinemacro] +<a id="{1}"></a>[{1}] +# xref:id[text] +[xref-inlinemacro] +<a href="#{target}">{0=[{target}]}</a> +# <<id,text>> +[xref2-inlinemacro] +<a href="#{1}">{2=[{1}]}</a> + +# Special word substitution. +[emphasizedwords] +<em>{words}</em> +[monospacedwords] +<span class="monospaced">{words}</span> +[strongwords] +<strong>{words}</strong> + +# Paragraph substitution. +[paragraph] +<div class="paragraph{role? {role}}"{id? id="{id}"}>{title?<div class="title">{title}</div>}<p> +| +</p></div> + +[admonitionparagraph] +template::[admonitionblock] + +# Delimited blocks. +[listingblock] +<div class="listingblock{role? {role}}"{id? id="{id}"}> +<div class="title">{caption=}{title}</div> +<div class="content monospaced"> +<pre> +| +</pre> +</div></div> + +[literalblock] +<div class="literalblock{role? {role}}"{id? id="{id}"}> +<div class="title">{title}</div> +<div class="content monospaced"> +<pre> +| +</pre> +</div></div> + +[sidebarblock] +<div class="sidebarblock{role? {role}}"{id? id="{id}"}> +<div class="content"> +<div class="title">{title}</div> +| +</div></div> + +[openblock] +<div class="openblock{role? {role}}"{id? id="{id}"}> +<div class="title">{title}</div> +<div class="content"> +| +</div></div> + +[partintroblock] +template::[openblock] + +[abstractblock] +template::[quoteblock] + +[quoteblock] +<div class="quoteblock{role? {role}}"{id? id="{id}"}> +<div class="title">{title}</div> +<div class="content"> +| +</div> +<div class="attribution"> +<em>{citetitle}</em>{attribution?<br>} +— {attribution} +</div></div> + +[verseblock] +<div class="verseblock{role? {role}}"{id? id="{id}"}> +<div class="title">{title}</div> +<pre class="content"> +| +</pre> +<div class="attribution"> +<em>{citetitle}</em>{attribution?<br>} +— {attribution} +</div></div> + +[exampleblock] +<div class="exampleblock{role? {role}}"{id? id="{id}"}> +<div class="title">{caption={example-caption} {counter:example-number}. }{title}</div> +<div class="content"> +| +</div></div> + +[admonitionblock] +<div class="admonitionblock{role? {role}}"{id? id="{id}"}> +<table><tr> +<td class="icon"> +{data-uri%}{icons#}<img src="{icon={iconsdir}/{name}.png}" alt="{caption}"> +{data-uri#}{icons#}<img alt="{caption}" src="data:image/png;base64, +{data-uri#}{icons#}{sys:python -uc "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join("{indir={outdir}}","{icon={iconsdir}/{name}.png}")}"}"> +{icons%}<div class="title">{caption}</div> +</td> +<td class="content"> +<div class="title">{title}</div> +| +</td> +</tr></table> +</div> + +# Tables. +[tabletags-default] +colspec=<col{autowidth-option! style="width:{colpcwidth}%;"}> +bodyrow=<tr>|</tr> +headdata=<th class="tableblock halign-{halign=left} valign-{valign=top}" {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }>|</th> +bodydata=<td class="tableblock halign-{halign=left} valign-{valign=top}" {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }>|</td> +paragraph=<p class="tableblock">|</p> + +[tabletags-header] +paragraph=<p class="tableblock header">|</p> + +[tabletags-emphasis] +paragraph=<p class="tableblock"><em>|</em></p> + +[tabletags-strong] +paragraph=<p class="tableblock"><strong>|</strong></p> + +[tabletags-monospaced] +paragraph=<p class="tableblock monospaced">|</p> + +[tabletags-verse] +bodydata=<td class="tableblock halign-{halign=left} valign-{valign=top}" {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }><div class="verse">|</div></td> +paragraph= + +[tabletags-literal] +bodydata=<td class="tableblock halign-{halign=left} valign-{valign=top}" {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }><div class="literal monospaced"><pre>|</pre></div></td> +paragraph= + +[tabletags-asciidoc] +bodydata=<td class="tableblock halign-{halign=left} valign-{valign=top}" {colspan@1::colspan="{colspan}" }{rowspan@1::rowspan="{rowspan}" }><div>|</div></td> +paragraph= + +[table] +<table class="tableblock frame-{frame=all} grid-{grid=all}{role? {role}}"{id? id="{id}"} +style=" +margin-left:{align@left:0}{align@center|right:auto}; margin-right:{align@left|center:auto}{align@right:0}; +float:{float}; +{autowidth-option%}width:{tablepcwidth}%; +{autowidth-option#}{width#style=width:{tablepcwidth}%;} +"> +<caption class="title">{caption={table-caption} {counter:table-number}. }{title}</caption> +{colspecs} +{headrows#}<thead> +{headrows} +{headrows#}</thead> +{footrows#}<tfoot> +{footrows} +{footrows#}</tfoot> +<tbody> +{bodyrows} +</tbody> +</table> + +#-------------------------------------------------------------------- +# Deprecated old table definitions. +# + +[miscellaneous] +# Screen width in pixels. +pagewidth=800 +pageunits=px + +[old_tabledef-default] +template=old_table +colspec=<col style="width:{colwidth}{pageunits};" /> +bodyrow=<tr>|</tr> +headdata=<th class="tableblock halign-{colalign=left}">|</th> +footdata=<td class="tableblock halign-{colalign=left}">|</td> +bodydata=<td class="tableblock halign-{colalign=left}">|</td> + +[old_table] +<table class="tableblock frame-{frame=all} grid-{grid=all}"{id? id="{id}"}> +<caption class="title">{caption={table-caption}}{title}</caption> +{colspecs} +{headrows#}<thead> +{headrows} +{headrows#}</thead> +{footrows#}<tfoot> +{footrows} +{footrows#}</tfoot> +<tbody style="vertical-align:top;"> +{bodyrows} +</tbody> +</table> + +# End of deprecated old table definitions. +#-------------------------------------------------------------------- + +[floatingtitle] +<h{level@0:1}{level@1:2}{level@2:3}{level@3:4}{level@4:5}{id? id="{id}"} class="float">{title}</h{level@0:1}{level@1:2}{level@2:3}{level@3:4}{level@4:5}> + +[preamble] +# Untitled elements between header and first section title. +<div id="preamble"> +<div class="sectionbody"> +| +</div> +</div> + +# Document sections. +[sect0] +<h1{id? id="{id}"}>{title}</h1> +| + +[sect1] +<div class="sect1{style? {style}}{role? {role}}"> +<h2{id? id="{id}"}>{numbered?{sectnum} }{title}</h2> +<div class="sectionbody"> +| +</div> +</div> + +[sect2] +<div class="sect2{style? {style}}{role? {role}}"> +<h3{id? id="{id}"}>{numbered?{sectnum} }{title}</h3> +| +</div> + +[sect3] +<div class="sect3{style? {style}}{role? {role}}"> +<h4{id? id="{id}"}>{numbered?{sectnum} }{title}</h4> +| +</div> + +[sect4] +<div class="sect4{style? {style}}{role? {role}}"> +<h5{id? id="{id}"}>{title}</h5> +| +</div> + +[appendix] +<div class="sect1{style? {style}}{role? {role}}"> +<h2{id? id="{id}"}>{numbered?{sectnum} }{appendix-caption} {counter:appendix-number:A}: {title}</h2> +<div class="sectionbody"> +| +</div> +</div> + +[toc] +<div id="toc"> + <div id="toctitle">{toc-title}</div> + <noscript><p><b>JavaScript must be enabled in your browser to display the table of contents.</b></p></noscript> +</div> + +[header] +<!DOCTYPE html> +<html lang="{lang=en}"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset={encoding}"> +<meta name="generator" content="AsciiDoc {asciidoc-version}"> +<meta name="description" content="{description}"> +<meta name="keywords" content="{keywords}"> +<title>{title} +{title%}{doctitle=} +ifdef::linkcss[] + +{doctype-manpage} +ifdef::pygments[] +ifdef::toc2[] + +endif::linkcss[] +ifndef::linkcss[] + +endif::linkcss[] +ifndef::disable-javascript[] +ifdef::linkcss[] + + +endif::linkcss[] +ifndef::linkcss[] + +endif::linkcss[] +endif::disable-javascript[] +ifdef::asciimath[] +ifdef::linkcss[] + +endif::linkcss[] +ifndef::linkcss[] + +endif::linkcss[] +endif::asciimath[] +ifdef::latexmath[] +ifdef::linkcss[] + +endif::linkcss[] +ifndef::linkcss[] + +endif::linkcss[] +endif::latexmath[] +{docinfo1,docinfo2#}{include:{docdir}/docinfo.html} +{docinfo,docinfo2#}{include:{docdir}/{docname}-docinfo.html} + + +# Article, book header. +ifndef::doctype-manpage[] + +endif::doctype-manpage[] +# Man page header. +ifdef::doctype-manpage[] + +endif::doctype-manpage[] +
+ +[footer] +
+{disable-javascript%

} + + + + +ifdef::doctype-manpage[] +[synopsis] +template::[sect1] +endif::doctype-manpage[] + diff --git a/doc/www/javascripts/asciidoc.js b/doc/www/javascripts/asciidoc.js new file mode 100644 index 00000000..2ad6c41c --- /dev/null +++ b/doc/www/javascripts/asciidoc.js @@ -0,0 +1,189 @@ +var asciidoc = { // Namespace. + +///////////////////////////////////////////////////////////////////// +// Table Of Contents generator +///////////////////////////////////////////////////////////////////// + +/* Author: Mihai Bazon, September 2002 + * http://students.infoiasi.ro/~mishoo + * + * Table Of Content generator + * Version: 0.4 + * + * Feel free to use this script under the terms of the GNU General Public + * License, as long as you do not remove or alter this notice. + */ + + /* modified by Troy D. Hanson, September 2006. License: GPL */ + /* modified by Stuart Rackham, 2006, 2009. License: GPL */ + +// toclevels = 1..4. +toc: function (toclevels) { + + function getText(el) { + var text = ""; + for (var i = el.firstChild; i != null; i = i.nextSibling) { + if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants. + text += i.data; + else if (i.firstChild != null) + text += getText(i); + } + return text; + } + + function TocEntry(el, text, toclevel) { + this.element = el; + this.text = text; + this.toclevel = toclevel; + } + + function tocEntries(el, toclevels) { + var result = new Array; + var re = new RegExp('[hH]([2-'+(toclevels+1)+'])'); + // Function that scans the DOM tree for header elements (the DOM2 + // nodeIterator API would be a better technique but not supported by all + // browsers). + var iterate = function (el) { + for (var i = el.firstChild; i != null; i = i.nextSibling) { + if (i.nodeType == 1 /* Node.ELEMENT_NODE */) { + var mo = re.exec(i.tagName); + if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") { + result[result.length] = new TocEntry(i, getText(i), mo[1]-1); + } + iterate(i); + } + } + } + iterate(el); + return result; + } + + var toc = document.getElementById("toc"); + if (!toc) { + return; + } + + // Delete existing TOC entries in case we're reloading the TOC. + var tocEntriesToRemove = []; + var i; + for (i = 0; i < toc.childNodes.length; i++) { + var entry = toc.childNodes[i]; + if (entry.nodeName == 'DIV' + && entry.getAttribute("class") + && entry.getAttribute("class").match(/^toclevel/)) + tocEntriesToRemove.push(entry); + } + for (i = 0; i < tocEntriesToRemove.length; i++) { + toc.removeChild(tocEntriesToRemove[i]); + } + + // Rebuild TOC entries. + var entries = tocEntries(document.getElementById("content"), toclevels); + for (var i = 0; i < entries.length; ++i) { + var entry = entries[i]; + if (entry.element.id == "") + entry.element.id = "_toc_" + i; + var a = document.createElement("a"); + a.href = "#" + entry.element.id; + a.appendChild(document.createTextNode(entry.text)); + var div = document.createElement("div"); + div.appendChild(a); + div.className = "toclevel" + entry.toclevel; + toc.appendChild(div); + } + if (entries.length == 0) + toc.parentNode.removeChild(toc); +}, + + +///////////////////////////////////////////////////////////////////// +// Footnotes generator +///////////////////////////////////////////////////////////////////// + +/* Based on footnote generation code from: + * http://www.brandspankingnew.net/archive/2005/07/format_footnote.html + */ + +footnotes: function () { + // Delete existing footnote entries in case we're reloading the footnodes. + var i; + var noteholder = document.getElementById("footnotes"); + if (!noteholder) { + return; + } + var entriesToRemove = []; + for (i = 0; i < noteholder.childNodes.length; i++) { + var entry = noteholder.childNodes[i]; + if (entry.nodeName == 'DIV' && entry.getAttribute("class") == "footnote") + entriesToRemove.push(entry); + } + for (i = 0; i < entriesToRemove.length; i++) { + noteholder.removeChild(entriesToRemove[i]); + } + + // Rebuild footnote entries. + var cont = document.getElementById("content"); + var spans = cont.getElementsByTagName("span"); + var refs = {}; + var n = 0; + for (i=0; i" + n + "]"; + spans[i].setAttribute("data-note", note); + } + noteholder.innerHTML += + "
" + + "" + + n + ". " + note + "
"; + var id =spans[i].getAttribute("id"); + if (id != null) refs["#"+id] = n; + } + } + if (n == 0) + noteholder.parentNode.removeChild(noteholder); + else { + // Process footnoterefs. + for (i=0; i" + n + "]"; + } + } + } +}, + +install: function(toclevels) { + var timerId; + + function reinstall() { + asciidoc.footnotes(); + if (toclevels) { + asciidoc.toc(toclevels); + } + } + + function reinstallAndRemoveTimer() { + clearInterval(timerId); + reinstall(); + } + + timerId = setInterval(reinstall, 500); + if (document.addEventListener) + document.addEventListener("DOMContentLoaded", reinstallAndRemoveTimer, false); + else + window.onload = reinstallAndRemoveTimer; +} + +} diff --git a/doc/www/lang-en.conf b/doc/www/lang-en.conf new file mode 100644 index 00000000..9d1c6d4d --- /dev/null +++ b/doc/www/lang-en.conf @@ -0,0 +1,54 @@ +# +# AsciiDoc English language configuration file. +# + +[attributes] +# Captions, used by (X)HTML backends. +# Captions on RHS are displayed in outputs. +ifdef::basebackend-html[] + +caution-caption=Caution +important-caption=Important +note-caption=Note +tip-caption=Tip +warning-caption=Warning +figure-caption=Figure +table-caption=Table +example-caption=Example +toc-title=Table of Contents +appendix-caption=Appendix +# Man page NAME section title. +manname-title=NAME + +[footer-text] +Version {revnumber}{basebackend-xhtml11?
}{basebackend-xhtml11=
} +Last updated {docdate} {doctime} + +endif::basebackend-html[] + + +[specialsections] +# DocBook special sections. +# The regular expression on LHS is matched against source titles. +ifdef::basebackend-docbook[] + +ifdef::doctype-article[] +^Abstract$=abstract +endif::doctype-article[] + +ifdef::doctype-book[] +^Colophon$=colophon +^Dedication$=dedication +^Preface$=preface +endif::doctype-book[] + +^Index$=index +^(Bibliography|References)$=bibliography +^Glossary$=glossary +^Appendix [A-Z][:.](?P.*)$=appendix + +endif::basebackend-docbook[] + +ifdef::doctype-manpage[] +(?i)^SYNOPSIS$=synopsis +endif::doctype-manpage[] diff --git a/doc/www/netatalk-relnotes.conf b/doc/www/netatalk-relnotes.conf new file mode 100644 index 00000000..594ee2d6 --- /dev/null +++ b/doc/www/netatalk-relnotes.conf @@ -0,0 +1,285 @@ +# +# netatalk.conf +# +# Asciidoc global configuration file. +# css backend, generates XHTML 1.0 conformant markup. +# +# Included in css-embedded.conf +# + +# Start with the html backend configuration. +# include::html.conf[] + +[titles] +underlines="--","==","~~","^^","++" + +[glossary] +basebackend=css +basebackend-css= +basebackend-html +dtddecl=PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" + +[tags] +# Add title class. +ilist={title?<p class="listtitle"><b>{title}</b></p>}<ul>|</ul> +olist={title?<p class="listtitle"><b>{title}</b></p>}<ol>|</ol> +vlist={title?<p class="listtitle"><b>{title}</b></p>}<dl>|</dl> +qlist={title?<p class="listtitle"><b>{title}</b></p>}<ol>|</ol> + +[under-construction-blockmacro] +<p class="under-construction"> +This page is currently under construction.<br/> +Please return soon. +</p> + +[image-inlinemacro] +<a class="imagelink" href="{link}"> +# border="0" so broken IE6 does not put border round linked image. + <img src="{target}" alt="{1={target}}"{1? title="{1}"}{width? width="{width}"}{height? height="{height}"} border="0"/> +{link#}</a> + +[image-blockmacro] +<div class="image"> + <p>{link?<a class="imagelink" href="{link}">} +# border="0" so broken IE6 does not put border round linked image. + <img src="{target}" alt="{1={target}}"{1? title="{1}"}{width? width="{width}"}{height? height="{height}"} border="0"/> + {link?</a>}</p> + <p class="imagetitle"><b>Figure:</b> {title}</p> +</div> + +# DEPRECATED. +[graphic] +<div class="graphic"> + <p><img src="{target}" alt="{caption={target}}"/></p> + <p class="graphictitle"><b>Figure:</b> {title}</p> +</div> + +# Paragraph substitution. +[indentedparagraph] +<p class="blocktitle"><b>Example:</b> {title}</p> +#<div class="indentedparagraph"><pre>|</pre></div> +   <a href="|">|</a> + +# Delimited block substitution. +[indentedblock] +<p class="blocktitle"><b>Example:</b> {title}</p> +<div class="indentedblock"><pre> +| +</pre></div> + +[verbatimblock] +<p class="blocktitle"><b>Example:</b> {title}</p> +<div class="verbatimblock"><pre> +| +</pre></div> + +[sidebarblock] +<div class="sidebarblock"> +<p class="sidebartitle">{title}</p> +| +</div> + +[customblock] +| + +[table] +# Table captions not used because IE6 is broken. +<p class="tabletitle"><b>Table:</b> {title}</p> +# If you want styled table borders in IE use the following table tag: +# 1. Border style specified here rather than in CSS because IE6 is broken. +# 2. bordercolor attribute is IE specific and not valid XHTML 1.0. +#<table rules="groups" border="2" bordercolor="green" frame="hsides" +# cellspacing="0" cellpadding="4"> +# +# Use this in preference to be strictly XHTML 1.0 conformant. +<table rules="groups" frame="{noborders?void}{noborders!hsides}" cellspacing="0" cellpadding="4"> +{headrows#}<thead{noborders? style="border-width: 0;"}> +{headrows} +{headrows#}</thead> +{footrows#}<tfoot{noborders? style="border-width: 0;"}> +{footrows} +{footrows#}</tfoot> +<tbody{noborders? style="border-width: 0;"}> +{bodyrows} +</tbody> +</table> + +#------------------------- +# article and book document types +# Both use the article.css stylesheet +#------------------------- +ifndef::doctype-manpage[] + +[header] +<!DOCTYPE html {dtddecl}> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<meta name="description" content="Netatalk - Unix file and print services for Apple clients" /> +<meta name="keywords" content="Netatalk, AFP, AFP Server, File Server, PAP, Print Server, Appletalk, Mac, OSX, OS X, OS9, OS 9" /> +<meta name="language" content="EN" /> +<meta name="publisher" content="netatalk.sourceforge.net" /> +<meta name="robots" content="Follow" /> +<link rel="stylesheet" type="text/css" charset="iso-8859-1" href="/css/site.css" /> +<link rel="stylesheet" type="text/css" charset="iso-8859-1" href="/css/printer.css" media="print" /> +<link rel="alternate stylesheet" type="text/css" charset="iso-8859-1" href="/css/printer.css" title="Printer" /> +<link rel="copyright" title="GNU General Public License" href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt" /> +<link rel="author" title="The Netatalk Development Team" href="http://netatalk.sf.net" /> +<link rel="home" href="index.php" title="Netatalk Home" /> +<link rel="home" href="http://www.sourceforge.net/projects/netatalk" title="Netatalk Sourceforge" /> +<link rel="bookmark" href="http://sourceforge.net/project/showfiles.php?group_id=8642" title="Downloads" /> + +<title>Netatalk Release Notes + + + +
+

netatalk.sourceforge.net

+
+ +
+

{doctitle}

+ +[footer] +
+ + + + +[preamble] +# Untitled elements between header and first section title. +
+| +
+ +[sect0] +

{1?}{title}

+| + +[sect1] +

{1?}{section-numbers?{sectnum} }{title}

+| + +[sect2] +

{1?}{section-numbers?{sectnum} }{title}

+| + +[sect3] +

{1?}{section-numbers?{sectnum} }{title}

+| + +[sect4] +
{1?}{title}
+| + +endif::doctype-manpage[] + +#------------------------- +# manpage document type +#------------------------- +ifdef::doctype-manpage[] + +[header] + + + + + + + + +{mantitle} + + +
+

{doctitle} Manual Page

+

NAME

+

{manname} - + {manpurpose} +

+ +[footer] + +
+ + + +# Section macros +[sect-synopsis] +
+

SYNOPSIS

+| +
+ +[sect1] +

{1?}{title}

+| + +[sect2] +

{1?}{title}

+| + +[sect3] +

{1?}{title}

+| + +[sect4] +
{1?}{title}
+| + +endif::doctype-manpage[] diff --git a/doc/www/stylesheets/asciidoc.css b/doc/www/stylesheets/asciidoc.css new file mode 100644 index 00000000..1475be71 --- /dev/null +++ b/doc/www/stylesheets/asciidoc.css @@ -0,0 +1,508 @@ +/* Shared CSS for AsciiDoc xhtml11 and html5 backends */ + +/* Default font. */ +body { + font-family: Georgia,serif; +} + +/* Title font. */ +h1, h2, h3, h4, h5, h6, +div.title, caption.title, +thead, p.table.header, +#toctitle, +#author, #revnumber, #revdate, #revremark, +#footer { + font-family: Arial,Helvetica,sans-serif; +} + +body { + margin: 1em 5% 1em 5%; +} + +a { + color: blue; + text-decoration: underline; +} +a:visited { + color: fuchsia; +} + +em { + font-style: italic; + color: navy; +} + +strong { + font-weight: bold; + color: #083194; +} + +h1, h2, h3, h4, h5, h6 { + color: #527bbd; + margin-top: 1.2em; + margin-bottom: 0.5em; + line-height: 1.3; +} + +h1, h2, h3 { + border-bottom: 2px solid silver; +} +h2 { + padding-top: 0.5em; +} +h3 { + float: left; +} +h3 + * { + clear: left; +} +h5 { + font-size: 1.0em; +} + +div.sectionbody { + margin-left: 0; +} + +hr { + border: 1px solid silver; +} + +p { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +ul, ol, li > p { + margin-top: 0; +} +ul > li { color: #aaa; } +ul > li > * { color: black; } + +pre { + padding: 0; + margin: 0; +} + +#author { + color: #527bbd; + font-weight: bold; + font-size: 1.1em; +} +#email { +} +#revnumber, #revdate, #revremark { +} + +#footer { + font-size: small; + border-top: 2px solid silver; + padding-top: 0.5em; + margin-top: 4.0em; +} +#footer-text { + float: left; + padding-bottom: 0.5em; +} +#footer-badges { + float: right; + padding-bottom: 0.5em; +} + +#preamble { + margin-top: 1.5em; + margin-bottom: 1.5em; +} +div.imageblock, div.exampleblock, div.verseblock, +div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock, +div.admonitionblock { + margin-top: 1.0em; + margin-bottom: 1.5em; +} +div.admonitionblock { + margin-top: 2.0em; + margin-bottom: 2.0em; + margin-right: 10%; + color: #606060; +} + +div.content { /* Block element content. */ + padding: 0; +} + +/* Block element titles. */ +div.title, caption.title { + color: #527bbd; + font-weight: bold; + text-align: left; + margin-top: 1.0em; + margin-bottom: 0.5em; +} +div.title + * { + margin-top: 0; +} + +td div.title:first-child { + margin-top: 0.0em; +} +div.content div.title:first-child { + margin-top: 0.0em; +} +div.content + div.title { + margin-top: 0.0em; +} + +div.sidebarblock > div.content { + background: #ffffee; + border: 1px solid #dddddd; + border-left: 4px solid #f0f0f0; + padding: 0.5em; +} + +div.listingblock > div.content { + border: 1px solid #dddddd; + border-left: 5px solid #f0f0f0; + background: #f8f8f8; + padding: 0.5em; +} + +div.quoteblock, div.verseblock { + padding-left: 1.0em; + margin-left: 1.0em; + margin-right: 10%; + border-left: 5px solid #f0f0f0; + color: #777777; +} + +div.quoteblock > div.attribution { + padding-top: 0.5em; + text-align: right; +} + +div.verseblock > pre.content { + font-family: inherit; + font-size: inherit; +} +div.verseblock > div.attribution { + padding-top: 0.75em; + text-align: left; +} +/* DEPRECATED: Pre version 8.2.7 verse style literal block. */ +div.verseblock + div.attribution { + text-align: left; +} + +div.admonitionblock .icon { + vertical-align: top; + font-size: 1.1em; + font-weight: bold; + text-decoration: underline; + color: #527bbd; + padding-right: 0.5em; +} +div.admonitionblock td.content { + padding-left: 0.5em; + border-left: 3px solid #dddddd; +} + +div.exampleblock > div.content { + border-left: 3px solid #dddddd; + padding-left: 0.5em; +} + +div.imageblock div.content { padding-left: 0; } +span.image img { border-style: none; } +a.image:visited { color: white; } + +dl { + margin-top: 0.8em; + margin-bottom: 0.8em; +} +dt { + margin-top: 0.5em; + margin-bottom: 0; + font-style: normal; + color: navy; +} +dd > *:first-child { + margin-top: 0.1em; +} + +ul, ol { + list-style-position: outside; +} +ol.arabic { + list-style-type: decimal; +} +ol.loweralpha { + list-style-type: lower-alpha; +} +ol.upperalpha { + list-style-type: upper-alpha; +} +ol.lowerroman { + list-style-type: lower-roman; +} +ol.upperroman { + list-style-type: upper-roman; +} + +div.compact ul, div.compact ol, +div.compact p, div.compact p, +div.compact div, div.compact div { + margin-top: 0.1em; + margin-bottom: 0.1em; +} + +tfoot { + font-weight: bold; +} +td > div.verse { + white-space: pre; +} + +div.hdlist { + margin-top: 0.8em; + margin-bottom: 0.8em; +} +div.hdlist tr { + padding-bottom: 15px; +} +dt.hdlist1.strong, td.hdlist1.strong { + font-weight: bold; +} +td.hdlist1 { + vertical-align: top; + font-style: normal; + padding-right: 0.8em; + color: navy; +} +td.hdlist2 { + vertical-align: top; +} +div.hdlist.compact tr { + margin: 0; + padding-bottom: 0; +} + +.comment { + background: yellow; +} + +.footnote, .footnoteref { + font-size: 0.8em; +} + +span.footnote, span.footnoteref { + vertical-align: super; +} + +#footnotes { + margin: 20px 0 20px 0; + padding: 7px 0 0 0; +} + +#footnotes div.footnote { + margin: 0 0 5px 0; +} + +#footnotes hr { + border: none; + border-top: 1px solid silver; + height: 1px; + text-align: left; + margin-left: 0; + width: 20%; + min-width: 100px; +} + +div.colist td { + padding-right: 0.5em; + padding-bottom: 0.3em; + vertical-align: top; +} +div.colist td img { + margin-top: 0.3em; +} + +@media print { + #footer-badges { display: none; } +} + +#toc { + margin-bottom: 2.5em; +} + +#toctitle { + color: #527bbd; + font-size: 1.1em; + font-weight: bold; + margin-top: 1.0em; + margin-bottom: 0.1em; +} + +div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 { + margin-top: 0; + margin-bottom: 0; +} +div.toclevel2 { + margin-left: 2em; + font-size: 0.9em; +} +div.toclevel3 { + margin-left: 4em; + font-size: 0.9em; +} +div.toclevel4 { + margin-left: 6em; + font-size: 0.9em; +} + +span.aqua { color: aqua; } +span.black { color: black; } +span.blue { color: blue; } +span.fuchsia { color: fuchsia; } +span.gray { color: gray; } +span.green { color: green; } +span.lime { color: lime; } +span.maroon { color: maroon; } +span.navy { color: navy; } +span.olive { color: olive; } +span.purple { color: purple; } +span.red { color: red; } +span.silver { color: silver; } +span.teal { color: teal; } +span.white { color: white; } +span.yellow { color: yellow; } + +span.aqua-background { background: aqua; } +span.black-background { background: black; } +span.blue-background { background: blue; } +span.fuchsia-background { background: fuchsia; } +span.gray-background { background: gray; } +span.green-background { background: green; } +span.lime-background { background: lime; } +span.maroon-background { background: maroon; } +span.navy-background { background: navy; } +span.olive-background { background: olive; } +span.purple-background { background: purple; } +span.red-background { background: red; } +span.silver-background { background: silver; } +span.teal-background { background: teal; } +span.white-background { background: white; } +span.yellow-background { background: yellow; } + +span.big { font-size: 2em; } +span.small { font-size: 0.6em; } + +span.underline { text-decoration: underline; } +span.overline { text-decoration: overline; } +span.line-through { text-decoration: line-through; } + + +/* + * xhtml11 specific + * + * */ + +tt { + font-family: monospace; + font-size: inherit; + color: navy; +} + +div.tableblock { + margin-top: 1.0em; + margin-bottom: 1.5em; +} +div.tableblock > table { + border: 3px solid #527bbd; +} +thead, p.table.header { + font-weight: bold; + color: #527bbd; +} +p.table { + margin-top: 0; +} +/* Because the table frame attribute is overriden by CSS in most browsers. */ +div.tableblock > table[frame="void"] { + border-style: none; +} +div.tableblock > table[frame="hsides"] { + border-left-style: none; + border-right-style: none; +} +div.tableblock > table[frame="vsides"] { + border-top-style: none; + border-bottom-style: none; +} + + +/* + * html5 specific + * + * */ + +.monospaced { + font-family: monospace; + font-size: inherit; + color: navy; +} + +table.tableblock { + margin-top: 1.0em; + margin-bottom: 1.5em; +} +thead, p.tableblock.header { + font-weight: bold; + color: #527bbd; +} +p.tableblock { + margin-top: 0; +} +table.tableblock { + border-width: 3px; + border-spacing: 0px; + border-style: solid; + border-color: #527bbd; + border-collapse: collapse; +} +th.tableblock, td.tableblock { + border-width: 1px; + padding: 4px; + border-style: solid; + border-color: #527bbd; +} + +table.tableblock.frame-topbot { + border-left-style: hidden; + border-right-style: hidden; +} +table.tableblock.frame-sides { + border-top-style: hidden; + border-bottom-style: hidden; +} +table.tableblock.frame-none { + border-style: hidden; +} + +th.tableblock.halign-left, td.tableblock.halign-left { + text-align: left; +} +th.tableblock.halign-center, td.tableblock.halign-center { + text-align: center; +} +th.tableblock.halign-right, td.tableblock.halign-right { + text-align: right; +} + +th.tableblock.valign-top, td.tableblock.valign-top { + vertical-align: top; +} +th.tableblock.valign-middle, td.tableblock.valign-middle { + vertical-align: middle; +} +th.tableblock.valign-bottom, td.tableblock.valign-bottom { + vertical-align: bottom; +} diff --git a/macros/netatalk.m4 b/macros/netatalk.m4 index ad24d2bb..e5e309ff 100644 --- a/macros/netatalk.m4 +++ b/macros/netatalk.m4 @@ -1,5 +1,44 @@ dnl Kitchen sink for configuration macros +dnl Check for docbook +AC_DEFUN(AX_CHECK_DOCBOOK, [ + # It's just rude to go over the net to build + XSLTPROC_FLAGS=--nonet + DOCBOOK_ROOT= + + AC_ARG_WITH(docbook, + AS_HELP_STRING( + [--with-docbook], + [Path to Docbook XSL directory] + ), + [DOCBOOK_ROOT=$withval] + ) + + if test -n "$DOCBOOK_ROOT" ; then + AC_CHECK_PROG(XSLTPROC,xsltproc,xsltproc,) + XSLTPROC_WORKS=no + if test -n "$XSLTPROC"; then + AC_MSG_CHECKING([whether xsltproc works]) + DB_FILE="$DOCBOOK_ROOT/html/docbook.xsl" + $XSLTPROC $XSLTPROC_FLAGS $DB_FILE >/dev/null 2&>&1 << END + + + + +END + if test "$?" = 0; then + XSLTPROC_WORKS=yes + fi + AC_MSG_RESULT($XSLTPROC_WORKS) + fi + fi + + AM_CONDITIONAL(HAVE_XSLTPROC, test x"$XSLTPROC_WORKS" = x"yes") + AC_SUBST(XSLTPROC_FLAGS) + AC_SUBST(DOCBOOK_ROOT) + AC_SUBST(XSLTPROC) +]) + dnl Check for dtrace AC_DEFUN([AC_NETATALK_DTRACE], [ AC_ARG_WITH(dtrace, diff --git a/man/man1/ad.1 b/man/man1/ad.1 index 20b14364..0e51d128 100644 --- a/man/man1/ad.1 +++ b/man/man1/ad.1 @@ -3,11 +3,11 @@ .\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] .\" Generator: DocBook XSL Stylesheets v1.78.0 .\" Date: 02 Sep 2011 -.\" Manual: Netatalk 3.0 -.\" Source: Netatalk 3.0 +.\" Manual: Netatalk 3.0.4dev +.\" Source: Netatalk 3.0.4dev .\" Language: English .\" -.TH "AD" "1" "02 Sep 2011" "Netatalk 3.0" "Netatalk 3.0" +.TH "AD" "1" "02 Sep 2011" "Netatalk 3.0.4dev" "Netatalk 3.0.4dev" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- -- 2.39.2