From 39f9ee159c529b4ab6563a2ca7e1af94c1650993 Mon Sep 17 00:00:00 2001 From: Jephte CLAIN Date: Tue, 27 Aug 2013 15:14:44 +0400 Subject: [PATCH] importation initiale dans git --- .dokuwikigen | 60 + .nutools-bootstrap | 0 .udir | 31 + EnsureVM | 111 + SKvm | 189 + SVirtualBox | 127 + _root | 28 + authftp | 74 + bashrc | 6 + caturl | 81 + compileAndGo | 625 + cssh | 80 + doc/.udir | 6 + doc/DefaultTiddlers.twp | 25 + doc/EnsureVM.twp | 16 + doc/Main.twp | 46 + doc/MainMenu.twp | 25 + doc/RUNS.txt | 152 + doc/SKvm.twp | 30 + doc/SVirtualBox.twp | 27 + doc/SiteSubtitle.twp | 25 + doc/SiteTitle.twp | 25 + doc/SiteUrl.twp | 25 + doc/_root.twp | 12 + doc/authftp.twp | 18 + doc/caturl.twp | 14 + doc/compileAndGo.twp | 11 + doc/fconv.twp | 27 + doc/fnconv.twp | 25 + doc/geturl.twp | 14 + doc/mkRewriteRules.twp | 86 + doc/mkiso.twp | 18 + doc/mkurl.twp | 20 + doc/mkusfx.twp | 35 + doc/openurl.twp | 14 + doc/rmtildes.twp | 16 + doc/rruns.twp | 62 + doc/ruinst.twp | 38 + doc/runs.twp | 60 + doc/rwoinst.twp | 38 + doc/twsync.twp | 39 + doc/ubackup.twp | 24 + doc/uconf.twp | 51 + doc/ucrontab.twp | 90 + doc/udir.twp | 38 + doc/uenv.twp | 32 + doc/uinc.sh.twp | 47 + doc/uinc.twp | 47 + doc/uinst.sh.twp | 38 + doc/uinst.twp | 38 + doc/ujava.twp | 28 + doc/uldap.twp | 219 + doc/ulib.twp | 49 + doc/ulib_DEFAULTS.twp | 8 + doc/ulib_apache.twp | 41 + doc/ulib_base.twp | 1043 ++ doc/ulib_bash.twp | 8 + doc/ulib_compat.twp | 8 + doc/ulib_conf.twp | 157 + doc/ulib_crontab.twp | 10 + doc/ulib_debian.twp | 49 + doc/ulib_install.twp | 44 + doc/ulib_ipcalc.twp | 54 + doc/ulib_java.twp | 29 + doc/ulib_javaproperties.twp | 28 + doc/ulib_ldap.twp | 66 + doc/ulib_ldif.twp | 60 + doc/ulib_legacy.twp | 50 + doc/ulib_macosx.twp | 24 + doc/ulib_mkcrypt.twp | 9 + doc/ulib_modeline.twp | 10 + doc/ulib_network-manager-service.twp | 18 + doc/ulib_pkg.twp | 20 + doc/ulib_prefixes.twp | 13 + doc/ulib_pretty.twp | 19 + doc/ulib_runs.twp | 213 + doc/ulib_service.twp | 33 + doc/ulib_sysinfos.twp | 36 + doc/ulib_tiddlywiki.twp | 115 + doc/ulib_udir.twp | 61 + doc/ulib_uenv.twp | 8 + doc/ulib_uenv_update.twp | 26 + doc/ulib_uinc.twp | 9 + doc/ulib_uinst.twp | 19 + doc/ulib_ulib.twp | 33 + doc/ulib_ulibsh.twp | 15 + doc/ulib_vcs.twp | 89 + doc/ulib_virsh.twp | 22 + doc/ulib_webobjects.twp | 180 + doc/ulib_woinst.twp | 12 + doc/ulib_wondermonitor.twp | 219 + doc/ulib_wosign.twp | 19 + doc/ulib_wotaskd.twp | 12 + doc/ulibshell.twp | 23 + doc/ulibsync.twp | 18 + doc/umatch.twp | 29 + doc/umirror.twp | 17 + doc/update.sh | 157 + doc/uprefix.twp | 21 + doc/uproject.twp | 75 + doc/usysinfos.twp | 19 + doc/vzusage.twp | 25 + doc/woArchive.twp | 21 + doc/woSwitch.twp | 33 + doc/woctl.twp | 58 + doc/woinst.twp | 29 + doc/wosign.twp | 21 + dokuwiki | 806 ++ fconv | 134 + fnconv | 101 + geturl | 37 + legacy/.udir | 6 + legacy/instinc/base | 37 + legacy/instinc/envsetup | 66 + legacy/instinc/envsetup_update | 124 + legacy/instinc/httpd_profile | 64 + legacy/instinc/prefixes | 415 + legacy/instinc/uinstdir | 23 + legacy/instinc/wobase | 172 + legacy/instinc/woconf | 194 + legacy/instinc/wofunctions | 212 + legacy/instinc/womonitor | 570 + legacy/instinc/worestart | 88 + legacy/instinc/wosign | 184 + legacy/instinc/wotag | 163 + legacy/sysinc/base | 1033 ++ legacy/sysinc/basevars | 19 + legacy/sysinc/begingetopt | 3 + legacy/sysinc/begingetopt-- | 4 + legacy/sysinc/begingetopt--_help | 8 + legacy/sysinc/begingetopt_help | 7 + legacy/sysinc/crontab | 36 + legacy/sysinc/endgetopt | 18 + legacy/sysinc/endgetopt-- | 23 + legacy/sysinc/ewaitcmd.test | 63 + legacy/sysinc/functions | 1826 +++ legacy/sysinc/java | 278 + legacy/sysinc/private/init | 65 + legacy/sysinc/scripts | 157 + legacy/sysinc/system_caps | 439 + legacy/sysinc/usebash | 20 + legacy/sysinc/utools | 40 + legacy/twinc/TODO.html | 4050 ++++++ legacy/twinc/TODO.js | 440 + legacy/twinc/base.js | 985 ++ legacy/twinc/baseui.js | 210 + legacy/twinc/old.js | 87 + legacy/twinc/patches.js | 47 + legacy/twinc/tiddlywiki | 93 + legacy/vbsinc/base | 21 + legacy/vbsinc/java | 16 + lib/b36sha1.php | 94 + lib/b36sha1.py | 150 + lib/backup.commands.template | 14 + lib/backup.excludes.template | 9 + lib/backup.includes.template | 14 + lib/backup.mount | 70 + lib/backup.umount | 47 + lib/bashrc.d/color_ls.[Darwin] | 5 + lib/bashrc.d/color_ls.[Linux] | 10 + lib/bashrc.d/color_ls.[SunOS] | 5 + lib/bashrc.d/confirm_rm,mv,cp | 7 + lib/bashrc.d/ls_options | 8 + lib/bashrc.d/nutools.userconf | 13 + lib/default/authftp | 7 + lib/default/dokuwiki | 19 + lib/default/mediawiki | 10 + lib/default/proxy | 18 + lib/default/pubkeys | 5 + lib/default/runs | 18 + lib/default/ubackup | 38 + lib/default/uldap | 38 + lib/init.d/install-kvm-stop-all | 10 + lib/init.d/install-openvz-fix-etchosts | 10 + lib/init.d/kvm-stop-all | 38 + lib/init.d/openvz-fix-etchosts | 63 + lib/makeself-2.1.5/COPYING | 341 + lib/makeself-2.1.5/README | 307 + lib/makeself-2.1.5/TODO | 6 + lib/makeself-2.1.5/makeself-header.sh | 419 + lib/makeself-2.1.5/makeself-header.sh.orig | 401 + lib/makeself-2.1.5/makeself.1 | 76 + lib/makeself-2.1.5/makeself.lsm | 16 + lib/makeself-2.1.5/makeself.sh | 419 + lib/makeself-2.1.5/makeself.sh.orig | 407 + lib/makeself.url | 2 + lib/mkselfinst | 62 + lib/nutoolsrc | 14 + lib/profile.d/bash_prompt | 179 + lib/profile.d/bin_in_path | 6 + lib/profile.d/dbus | 8 + lib/profile.d/histcontrol | 2 + lib/profile.d/local_bin_in_path.[Darwin] | 3 + lib/profile.d/nutools | 72 + lib/profile.d/nutools.userconf | 28 + lib/profile.d/proxy | 58 + lib/profile.d/runs.userconf | 5 + lib/profile.d/webobjects | 10 + lib/profile.d/webobjects.userconf | 7 + lib/pywrapper | 21 + lib/reptyr/.gitignore | 3 + lib/reptyr/COPYING | 19 + lib/reptyr/ChangeLog | 17 + lib/reptyr/Makefile | 23 + lib/reptyr/NOTES | 13 + lib/reptyr/README.md | 100 + lib/reptyr/arch/amd64.h | 97 + lib/reptyr/arch/arm.h | 58 + lib/reptyr/arch/default-syscalls.h | 32 + lib/reptyr/arch/i386.h | 42 + lib/reptyr/arch/x86_common.h | 59 + lib/reptyr/attach.c | 513 + lib/reptyr/ptrace.c | 341 + lib/reptyr/ptrace.h | 82 + lib/reptyr/reptyr.1 | 164 + lib/reptyr/reptyr.c | 284 + lib/reptyr/reptyr.fr.1 | 155 + lib/reptyr/reptyr.h | 29 + lib/reptyr/reptyr.patch | 96 + lib/template | 20 + lib/templates/auto | 155 + lib/templates/java | 285 + lib/templates/script.template | 117 + lib/templates/shell | 205 + lib/templates/templates.conf | 47 + lib/tiddlywiki/TiddlySaver.jar | Bin 0 -> 4982 bytes lib/tiddlywiki/download-latest.sh | 35 + lib/tiddlywiki/empty.html | 10147 ++++++++++++++++ lib/tiddlywiki/tiddlywiki.url | 2 + lib/uinst/conf | 48 + lib/uinst/rootconf | 72 + lib/uinst/system_caps.legacy | 1479 +++ mediawiki | 958 ++ mkRewriteRules | 291 + mkiso | 40 + mkurl | 71 + mkusfx | 129 + mocifs | 101 + modav | 115 + moiso | 77 + mossh | 134 + mysqlcsv | 131 + mysqlloadcsv | 147 + openurl | 33 + profile | 8 + pyulib/.project | 17 + pyulib/.pydevproject | 10 + .../org.eclipse.core.resources.prefs | 3 + .../.settings/org.eclipse.core.runtime.prefs | 3 + pyulib/.udir | 6 + pyulib/MANIFEST.in | 4 + pyulib/devel/TODO.txt | 5 + pyulib/devel/Tasks.txt | 72 + pyulib/devel/doctests | 29 + pyulib/devel/idleshell | 10 + pyulib/devel/mkdiff-webpy.sh | 15 + pyulib/devel/pt | 6 + pyulib/devel/pyshell | 10 + pyulib/devel/python_interpreters | 29 + pyulib/devel/shell_init | 58 + pyulib/devel/spp | 59 + pyulib/devel/web.py-0.33.patch | 165 + pyulib/migrate/base.py | 44 + pyulib/migrate/tasks1/TODO.py | 745 ++ pyulib/migrate/tasks1/TODO_helper.py | 99 + pyulib/migrate/tasks1/tiddlywiki.py | 49 + pyulib/migrate/tasks1/tiddlywiki2.py | 293 + pyulib/migrate/tasks2/Tasks.py | 1711 +++ pyulib/migrate/tasks2/TiddlyWiki.py | 940 ++ pyulib/migrate/wo.py | 366 + pyulib/setup.py | 111 + pyulib/src/ULIB_CONFIG.py | 26 + pyulib/src/ULIB_CONFIG.pyc | Bin 0 -> 496 bytes pyulib/src/i_need_py23.py | 10 + pyulib/src/i_need_py23.pyc | Bin 0 -> 458 bytes pyulib/src/i_need_py24.py | 10 + pyulib/src/i_need_py25.py | 10 + pyulib/src/i_need_py26.py | 10 + pyulib/src/uapps/__init__.py | 1 + pyulib/src/uapps/jclain_license.txt | 104 + pyulib/src/uapps/plbck.py | 449 + pyulib/src/uapps/plver.py | 165 + pyulib/src/uapps/pyucontacts.py | 328 + pyulib/src/uapps/pyurelease.py | 811 ++ pyulib/src/uapps/pyutasks.py | 96 + pyulib/src/uapps/pyuupdate_inc.py | 715 ++ pyulib/src/uapps/tasks/__init__.py | 5 + pyulib/src/uapps/tasks/httpd/__init__.py | 3 + pyulib/src/uapps/tasks/httpd/server.py | 572 + .../uapps/tasks/httpd/static/blueprint/ie.css | 35 + .../blueprint/plugins/buttons/icons/cross.png | Bin 0 -> 655 bytes .../blueprint/plugins/buttons/icons/key.png | Bin 0 -> 455 bytes .../blueprint/plugins/buttons/icons/tick.png | Bin 0 -> 537 bytes .../blueprint/plugins/buttons/readme.txt | 32 + .../blueprint/plugins/buttons/screen.css | 97 + .../blueprint/plugins/fancy-type/readme.txt | 14 + .../blueprint/plugins/fancy-type/screen.css | 71 + .../plugins/link-icons/icons/doc.png | Bin 0 -> 777 bytes .../plugins/link-icons/icons/email.png | Bin 0 -> 641 bytes .../plugins/link-icons/icons/external.png | Bin 0 -> 46848 bytes .../plugins/link-icons/icons/feed.png | Bin 0 -> 691 bytes .../blueprint/plugins/link-icons/icons/im.png | Bin 0 -> 741 bytes .../plugins/link-icons/icons/pdf.png | Bin 0 -> 591 bytes .../plugins/link-icons/icons/visited.png | Bin 0 -> 46990 bytes .../plugins/link-icons/icons/xls.png | Bin 0 -> 663 bytes .../blueprint/plugins/link-icons/readme.txt | 18 + .../blueprint/plugins/link-icons/screen.css | 40 + .../static/blueprint/plugins/rtl/readme.txt | 10 + .../static/blueprint/plugins/rtl/screen.css | 110 + .../blueprint/plugins/tabs/AUTHORS.textile | 3 + .../blueprint/plugins/tabs/README.textile | 42 + .../static/blueprint/plugins/tabs/screen.css | 63 + .../tasks/httpd/static/blueprint/print.css | 29 + .../tasks/httpd/static/blueprint/screen.css | 257 + .../tasks/httpd/static/blueprint/src/grid.png | Bin 0 -> 161 bytes .../src/uapps/tasks/httpd/static/bpsuppl.css | 27 + .../tasks/httpd/static/clock-samples.html | 28 + pyulib/src/uapps/tasks/httpd/static/clock.css | 17 + .../uapps/tasks/httpd/static/clock/add.png | Bin 0 -> 925 bytes .../uapps/tasks/httpd/static/clock/base.png | Bin 0 -> 882 bytes .../uapps/tasks/httpd/static/clock/delete.png | Bin 0 -> 952 bytes .../uapps/tasks/httpd/static/clock/edit.png | Bin 0 -> 967 bytes .../uapps/tasks/httpd/static/clock/error.png | Bin 0 -> 953 bytes .../src/uapps/tasks/httpd/static/clock/go.png | Bin 0 -> 959 bytes .../uapps/tasks/httpd/static/clock/link.png | Bin 0 -> 961 bytes .../uapps/tasks/httpd/static/clock/pause.png | Bin 0 -> 927 bytes .../uapps/tasks/httpd/static/clock/play.png | Bin 0 -> 943 bytes .../uapps/tasks/httpd/static/clock/red.png | Bin 0 -> 889 bytes .../uapps/tasks/httpd/static/clock/start.png | Bin 0 -> 1006 bytes .../uapps/tasks/httpd/static/clock/stop.png | Bin 0 -> 980 bytes pyulib/src/uapps/tasks/httpd/static/edit.css | 5 + .../src/uapps/tasks/httpd/static/favicon.ico | Bin 0 -> 1150 bytes pyulib/src/uapps/tasks/httpd/static/index.css | 101 + pyulib/src/uapps/tasks/httpd/static/index.js | 94 + .../src/uapps/tasks/httpd/static/index.min.js | 1 + .../tasks/httpd/static/jquery.hoverIntent.js | 111 + .../httpd/static/jquery.hoverIntent.min.js | 1 + pyulib/src/uapps/tasks/httpd/static/jquery.js | 4376 +++++++ .../uapps/tasks/httpd/static/jquery.min.js | 19 + .../tasks/httpd/static/jquery.tooltip.css | 9 + .../tasks/httpd/static/jquery.tooltip.js | 19 + .../tasks/httpd/static/jquery.tooltip.min.js | 1 + .../uapps/tasks/httpd/static/taskshttpd.js | 44 + .../tasks/httpd/static/taskshttpd.min.js | 1 + .../tasks/httpd/static/zoom-samples.html | 19 + pyulib/src/uapps/tasks/httpd/static/zoom.css | 8 + .../uapps/tasks/httpd/static/zoom/base.png | Bin 0 -> 692 bytes .../src/uapps/tasks/httpd/static/zoom/in.png | Bin 0 -> 725 bytes .../src/uapps/tasks/httpd/static/zoom/out.png | Bin 0 -> 708 bytes .../src/uapps/tasks/httpd/templates/edit.html | 47 + .../uapps/tasks/httpd/templates/index.html | 85 + .../uapps/tasks/httpd/templates/task.htmlc | 39 + pyulib/src/uapps/tasks/httpstore/__init__.py | 3 + pyulib/src/uapps/tasks/httpstore/server.py | 70 + .../tasks/httpstore/templates/index.html | 26 + pyulib/src/uapps/tasks/itfctl.py | 39 + pyulib/src/uapps/tasks/taskscli.py | 422 + pyulib/src/uapps/uencdetect.py | 46 + pyulib/src/uapps/uproject.py | 88 + pyulib/src/uapps/ur_license.txt | 28 + pyulib/src/uapps/urandomize.py | 111 + pyulib/src/ulib/__init__.py | 3 + pyulib/src/ulib/__init__.pyc | Bin 0 -> 170 bytes pyulib/src/ulib/all/__init__.py | 65 + pyulib/src/ulib/all/__init__.pyc | Bin 0 -> 2146 bytes pyulib/src/ulib/base/__init__.py | 4 + pyulib/src/ulib/base/__init__.pyc | Bin 0 -> 227 bytes pyulib/src/ulib/base/args.py | 614 + pyulib/src/ulib/base/args.pyc | Bin 0 -> 25491 bytes pyulib/src/ulib/base/base.py | 462 + pyulib/src/ulib/base/base.pyc | Bin 0 -> 23352 bytes pyulib/src/ulib/base/config.py | 811 ++ pyulib/src/ulib/base/config.pyc | Bin 0 -> 28861 bytes pyulib/src/ulib/base/control.py | 87 + pyulib/src/ulib/base/control.pyc | Bin 0 -> 4053 bytes pyulib/src/ulib/base/dates.py | 912 ++ pyulib/src/ulib/base/dates.pyc | Bin 0 -> 41075 bytes pyulib/src/ulib/base/editor.py | 131 + pyulib/src/ulib/base/editor.pyc | Bin 0 -> 4301 bytes pyulib/src/ulib/base/encdetect.py | 153 + pyulib/src/ulib/base/encdetect.pyc | Bin 0 -> 6608 bytes pyulib/src/ulib/base/encoding.py | 101 + pyulib/src/ulib/base/encoding.pyc | Bin 0 -> 3523 bytes pyulib/src/ulib/base/env.py | 54 + pyulib/src/ulib/base/env.pyc | Bin 0 -> 3140 bytes pyulib/src/ulib/base/files.py | 142 + pyulib/src/ulib/base/files.pyc | Bin 0 -> 6657 bytes pyulib/src/ulib/base/flock.py | 68 + pyulib/src/ulib/base/flock.pyc | Bin 0 -> 3256 bytes pyulib/src/ulib/base/functions.py | 58 + pyulib/src/ulib/base/functions.pyc | Bin 0 -> 1994 bytes pyulib/src/ulib/base/htmlentities.py | 360 + pyulib/src/ulib/base/htmlentities.pyc | Bin 0 -> 10703 bytes pyulib/src/ulib/base/i_need_py23.py | 7 + pyulib/src/ulib/base/i_need_py23.pyc | Bin 0 -> 362 bytes pyulib/src/ulib/base/i_need_py24.py | 7 + pyulib/src/ulib/base/i_need_py24.pyc | Bin 0 -> 362 bytes pyulib/src/ulib/base/i_need_py25.py | 7 + pyulib/src/ulib/base/i_need_py26.py | 7 + pyulib/src/ulib/base/input.py | 124 + pyulib/src/ulib/base/input.pyc | Bin 0 -> 5912 bytes pyulib/src/ulib/base/lines.py | 397 + pyulib/src/ulib/base/lines.pyc | Bin 0 -> 15539 bytes pyulib/src/ulib/base/output.py | 311 + pyulib/src/ulib/base/output.pyc | Bin 0 -> 18920 bytes pyulib/src/ulib/base/pager.py | 55 + pyulib/src/ulib/base/pager.pyc | Bin 0 -> 1784 bytes pyulib/src/ulib/base/paths.py | 206 + pyulib/src/ulib/base/paths.pyc | Bin 0 -> 8931 bytes pyulib/src/ulib/base/procs.py | 314 + pyulib/src/ulib/base/procs.pyc | Bin 0 -> 13576 bytes pyulib/src/ulib/base/pversion.py | 58 + pyulib/src/ulib/base/pversion.pyc | Bin 0 -> 2656 bytes pyulib/src/ulib/base/times.py | 215 + pyulib/src/ulib/base/times.pyc | Bin 0 -> 12202 bytes pyulib/src/ulib/base/tmpfiles.py | 157 + pyulib/src/ulib/base/tmpfiles.pyc | Bin 0 -> 8189 bytes pyulib/src/ulib/base/uio.py | 100 + pyulib/src/ulib/base/uio.pyc | Bin 0 -> 6335 bytes pyulib/src/ulib/base/words.py | 238 + pyulib/src/ulib/base/words.pyc | Bin 0 -> 9305 bytes pyulib/src/ulib/ext/__init__.py | 1 + pyulib/src/ulib/ext/__init__.pyc | Bin 0 -> 151 bytes pyulib/src/ulib/ext/optik141/README.txt | 160 + pyulib/src/ulib/ext/optik141/__init__.py | 31 + pyulib/src/ulib/ext/optik141/errors.py | 57 + pyulib/src/ulib/ext/optik141/help.py | 173 + pyulib/src/ulib/ext/optik141/option.py | 386 + pyulib/src/ulib/ext/optik141/option_parser.py | 771 ++ pyulib/src/ulib/ext/optik141/textwrap.py | 291 + pyulib/src/ulib/ext/simplejson/__init__.py | 318 + pyulib/src/ulib/ext/simplejson/__init__.pyc | Bin 0 -> 13426 bytes pyulib/src/ulib/ext/simplejson/_speedups.c | 2329 ++++ pyulib/src/ulib/ext/simplejson/decoder.py | 354 + pyulib/src/ulib/ext/simplejson/decoder.pyc | Bin 0 -> 12973 bytes pyulib/src/ulib/ext/simplejson/encoder.py | 440 + pyulib/src/ulib/ext/simplejson/encoder.pyc | Bin 0 -> 17660 bytes pyulib/src/ulib/ext/simplejson/scanner.py | 65 + pyulib/src/ulib/ext/simplejson/scanner.pyc | Bin 0 -> 3165 bytes pyulib/src/ulib/ext/tarfile/README.txt | 4 + pyulib/src/ulib/ext/tarfile/__init__.py | 1 + pyulib/src/ulib/ext/tarfile/gzip22.py | 392 + pyulib/src/ulib/ext/tarfile/tarfile.py | 2004 +++ pyulib/src/ulib/ext/web/__init__.py | 34 + pyulib/src/ulib/ext/web/__init__.pyc | Bin 0 -> 1030 bytes pyulib/src/ulib/ext/web/application.py | 678 ++ pyulib/src/ulib/ext/web/application.pyc | Bin 0 -> 26204 bytes pyulib/src/ulib/ext/web/browser.py | 236 + pyulib/src/ulib/ext/web/browser.pyc | Bin 0 -> 11864 bytes pyulib/src/ulib/ext/web/contrib/__init__.py | 0 pyulib/src/ulib/ext/web/contrib/template.py | 131 + pyulib/src/ulib/ext/web/db.py | 1168 ++ pyulib/src/ulib/ext/web/db.pyc | Bin 0 -> 45431 bytes pyulib/src/ulib/ext/web/debugerror.py | 356 + pyulib/src/ulib/ext/web/debugerror.pyc | Bin 0 -> 13325 bytes pyulib/src/ulib/ext/web/form.py | 367 + pyulib/src/ulib/ext/web/form.pyc | Bin 0 -> 18878 bytes pyulib/src/ulib/ext/web/http.py | 148 + pyulib/src/ulib/ext/web/http.pyc | Bin 0 -> 6273 bytes pyulib/src/ulib/ext/web/httpserver.py | 265 + pyulib/src/ulib/ext/web/httpserver.pyc | Bin 0 -> 11654 bytes pyulib/src/ulib/ext/web/net.py | 190 + pyulib/src/ulib/ext/web/net.pyc | Bin 0 -> 5982 bytes pyulib/src/ulib/ext/web/session.py | 319 + pyulib/src/ulib/ext/web/session.pyc | Bin 0 -> 14357 bytes pyulib/src/ulib/ext/web/template.py | 1447 +++ pyulib/src/ulib/ext/web/template.pyc | Bin 0 -> 58805 bytes pyulib/src/ulib/ext/web/test.py | 51 + pyulib/src/ulib/ext/web/utils.py | 1184 ++ pyulib/src/ulib/ext/web/utils.pyc | Bin 0 -> 40501 bytes pyulib/src/ulib/ext/web/webapi.py | 382 + pyulib/src/ulib/ext/web/webapi.pyc | Bin 0 -> 15755 bytes pyulib/src/ulib/ext/web/webopenid.py | 115 + pyulib/src/ulib/ext/web/webopenid.pyc | Bin 0 -> 5048 bytes pyulib/src/ulib/ext/web/wsgi.py | 66 + pyulib/src/ulib/ext/web/wsgi.pyc | Bin 0 -> 2565 bytes .../src/ulib/ext/web/wsgiserver/LICENSE.txt | 25 + .../src/ulib/ext/web/wsgiserver/__init__.py | 1794 +++ pyulib/src/ulib/formats/__init__.py | 39 + pyulib/src/ulib/formats/__init__.pyc | Bin 0 -> 1453 bytes pyulib/src/ulib/formats/base.py | 40 + pyulib/src/ulib/formats/base.pyc | Bin 0 -> 1714 bytes pyulib/src/ulib/formats/boolean.py | 22 + pyulib/src/ulib/formats/boolean.pyc | Bin 0 -> 1207 bytes pyulib/src/ulib/formats/datetime.py | 40 + pyulib/src/ulib/formats/datetime.pyc | Bin 0 -> 2264 bytes pyulib/src/ulib/formats/float.py | 27 + pyulib/src/ulib/formats/float.pyc | Bin 0 -> 1270 bytes pyulib/src/ulib/formats/integer.py | 27 + pyulib/src/ulib/formats/integer.pyc | Bin 0 -> 1283 bytes pyulib/src/ulib/formats/jour.py | 42 + pyulib/src/ulib/formats/jour.pyc | Bin 0 -> 1993 bytes pyulib/src/ulib/formats/strings.py | 102 + pyulib/src/ulib/formats/strings.pyc | Bin 0 -> 4973 bytes pyulib/src/ulib/formats/telephone.py | 38 + pyulib/src/ulib/formats/telephone.pyc | Bin 0 -> 1642 bytes pyulib/src/ulib/formats/weekday.py | 57 + pyulib/src/ulib/formats/weekday.pyc | Bin 0 -> 2900 bytes pyulib/src/ulib/gae/__init__.py | 4 + pyulib/src/ulib/gae/dao.py | 230 + pyulib/src/ulib/gae/datetime.py | 53 + pyulib/src/ulib/gae/pages.py | 131 + pyulib/src/ulib/json/__init__.py | 8 + pyulib/src/ulib/json/__init__.pyc | Bin 0 -> 435 bytes pyulib/src/ulib/optparse/__init__.py | 8 + pyulib/src/ulib/optparse/__init__.pyc | Bin 0 -> 443 bytes pyulib/src/ulib/p/__init__.py | 3 + pyulib/src/ulib/p/__init__.pyc | Bin 0 -> 172 bytes pyulib/src/ulib/p/dirconf.py | 125 + pyulib/src/ulib/p/dirconf.pyc | Bin 0 -> 6504 bytes pyulib/src/ulib/p/templ/__init__.py | 10 + pyulib/src/ulib/p/templ/base.py | 469 + pyulib/src/ulib/p/templ/htmltempl.py | 194 + pyulib/src/ulib/p/templ/markdowntempl.py | 83 + pyulib/src/ulib/p/templ/pytempl.py | 120 + pyulib/src/ulib/p/templ/shtempl.py | 58 + pyulib/src/ulib/p/templ/texttempl.py | 36 + pyulib/src/ulib/p/uinc/__init__.py | 10 + pyulib/src/ulib/p/uinc/__init__.pyc | Bin 0 -> 493 bytes pyulib/src/ulib/p/uinc/output.py | 130 + pyulib/src/ulib/p/uinc/output.pyc | Bin 0 -> 12135 bytes pyulib/src/ulib/p/uinc/spec.py | 155 + pyulib/src/ulib/p/uinc/spec.pyc | Bin 0 -> 7159 bytes pyulib/src/ulib/p/uinc/updater.py | 1144 ++ pyulib/src/ulib/p/uinc/updater.pyc | Bin 0 -> 43837 bytes pyulib/src/ulib/p/vcs/__init__.py | 31 + pyulib/src/ulib/p/vcs/base.py | 182 + pyulib/src/ulib/p/vcs/cvs.py | 127 + pyulib/src/ulib/p/vcs/svn.py | 240 + pyulib/src/ulib/p/wop/__init__.py | 7 + pyulib/src/ulib/p/wop/uinst.py | 32 + pyulib/src/ulib/sa/__init__.py | 4 + pyulib/src/ulib/sa/dao.py | 171 + pyulib/src/ulib/sa/datetime.py | 183 + pyulib/src/ulib/sa/props.py | 42 + pyulib/src/ulib/tasks/__init__.py | 17 + pyulib/src/ulib/tasks/base.py | 230 + pyulib/src/ulib/tasks/consts.py | 35 + pyulib/src/ulib/tasks/factories.py | 67 + pyulib/src/ulib/tasks/httpclient.py | 103 + pyulib/src/ulib/tasks/multiple.py | 135 + pyulib/src/ulib/tasks/projects.py | 75 + pyulib/src/ulib/tasks/simple.py | 146 + pyulib/src/ulib/tasks/store.py | 375 + pyulib/src/ulib/tasks/tasks.py | 1075 ++ pyulib/src/ulib/tasks/tasksctl.py | 105 + pyulib/src/ulib/tasks/tasksrc.py | 190 + pyulib/src/ulib/templ/__init__.py | 55 + pyulib/src/ulib/templ/base.py | 313 + pyulib/src/ulib/templ/htmltempl.py | 155 + pyulib/src/ulib/templ/javatempl.py | 134 + pyulib/src/ulib/templ/pytempl.py | 96 + pyulib/src/ulib/templ/wikitempl.py | 69 + pyulib/src/ulib/templ/wotempl.py | 125 + pyulib/src/ulib/web/__init__.py | 18 + pyulib/src/ulib/web/__init__.pyc | Bin 0 -> 560 bytes pyulib/src/ulib/web/api.py | 11 + pyulib/src/ulib/web/api.pyc | Bin 0 -> 566 bytes pyulib/src/ulib/web/pages.py | 511 + pyulib/src/ulib/web/pages.pyc | Bin 0 -> 24796 bytes pyulib/test/Contacts.txt | 84 + pyulib/test/base.py | 16 + pyulib/test/run_tests | 44 + pyulib/test/template.py | 19 + pyulib/test/test_all.py | 32 + pyulib/test/test_base/__init__.py | 18 + pyulib/test/test_base/base.py | 21 + pyulib/test/test_base/test.config | 13 + pyulib/test/test_base/test.sh_config | 13 + pyulib/test/test_base/test_args.py | 47 + pyulib/test/test_base/test_base.py | 330 + pyulib/test/test_base/test_config.py | 84 + pyulib/test/test_base/test_control.py | 20 + pyulib/test/test_base/test_dates.py | 20 + pyulib/test/test_base/test_editor.py | 20 + pyulib/test/test_base/test_encdetect.py | 20 + pyulib/test/test_base/test_encoding.py | 20 + pyulib/test/test_base/test_env.py | 20 + pyulib/test/test_base/test_files.py | 20 + pyulib/test/test_base/test_flock.py | 20 + pyulib/test/test_base/test_functions.py | 20 + pyulib/test/test_base/test_input.py | 20 + pyulib/test/test_base/test_iso8859.py | 20 + pyulib/test/test_base/test_lines.py | 20 + pyulib/test/test_base/test_output.py | 20 + pyulib/test/test_base/test_pager.py | 20 + pyulib/test/test_base/test_paths.py | 20 + pyulib/test/test_base/test_procs.py | 20 + pyulib/test/test_base/test_times.py | 20 + pyulib/test/test_base/test_tmpfiles.py | 20 + pyulib/test/test_base/test_uio.py | 20 + pyulib/test/test_base/test_words.py | 20 + reptyr.cgo | 1653 +++ rmtildes | 19 + rruns | 416 + ruinst | 251 + runs | 380 + rwoinst | 176 + twsync | 250 + uawk | 62 + ubackup | 193 + ucalc | 134 + uconf | 118 + ucrontab | 4360 +++++++ udaemon.cgo | 91 + udir | 210 + udist | 1804 +++ uenv | 103 + uinc | 9 + uinc.sh | 12 + uinst | 8 + uinst.sh | 11 + ujava | 68 + uldap | 1555 +++ ulib/DEFAULTS | 7 + ulib/PREFIXES-DEFAULTS | 17 + ulib/apache | 671 + ulib/auto | 31 + ulib/awk | 1388 +++ ulib/base | 3927 ++++++ ulib/bash | 13 + ulib/compat | 375 + ulib/conf | 611 + ulib/crontab | 250 + ulib/debian | 113 + ulib/install | 124 + ulib/ipcalc | 170 + ulib/java | 490 + ulib/javaproperties | 113 + ulib/json | 31 + ulib/ldap | 240 + ulib/ldif | 1128 ++ ulib/legacy | 579 + ulib/macosx | 118 + ulib/mkcrypt | 9 + ulib/modeline | 169 + ulib/network-manager-service | 32 + ulib/nutools/.udir | 6 + ulib/nutools/plbck | 6 + ulib/nutools/plver | 6 + ulib/nutools/pyulib | 37 + ulib/nutools/uencdetect | 6 + ulib/prefixes | 52 + ulib/pretty | 301 + ulib/redhat | 78 + ulib/runs | 1937 +++ ulib/service | 67 + ulib/support/install-pubkeys.sh | 3520 ++++++ ulib/support/mkcrypt.py | 30 + ulib/support/uinc.py | 81 + ulib/support/uinst2s | 32 + ulib/support/uinst2s_python | 21 + ulib/support/woinst2s | 232 + ulib/sysinfos | 384 + ulib/tiddlywiki | 424 + ulib/udir | 195 + ulib/uenv | 72 + ulib/uenv_update | 262 + ulib/uinc | 9 + ulib/uinst | 1349 ++ ulib/ulib | 102 + ulib/ulibsh | 94 + ulib/vcs | 624 + ulib/virsh | 40 + ulib/webobjects | 696 ++ ulib/woinst | 558 + ulib/wondermonitor | 733 ++ ulib/wosign | 195 + ulib/wotaskd | 57 + ulibshell | 97 + ulibsync | 40 + umatch | 136 + umirror | 36 + uprefix | 47 + uproject | 170 + urunserial | 171 + ussh | 162 + usysinfos | 30 + utempl | 100 + utrigger | 199 + vzusage | 207 + woArchive | 63 + woSwitch | 116 + woctl | 1022 ++ wofixsql.py | 81 + woinst | 8 + wosign | 79 + 687 files changed, 125329 insertions(+) create mode 100644 .dokuwikigen create mode 100644 .nutools-bootstrap create mode 100644 .udir create mode 100755 EnsureVM create mode 100755 SKvm create mode 100755 SVirtualBox create mode 100755 _root create mode 100755 authftp create mode 100644 bashrc create mode 100755 caturl create mode 100755 compileAndGo create mode 100755 cssh create mode 100644 doc/.udir create mode 100644 doc/DefaultTiddlers.twp create mode 100644 doc/EnsureVM.twp create mode 100644 doc/Main.twp create mode 100644 doc/MainMenu.twp create mode 100644 doc/RUNS.txt create mode 100644 doc/SKvm.twp create mode 100644 doc/SVirtualBox.twp create mode 100644 doc/SiteSubtitle.twp create mode 100644 doc/SiteTitle.twp create mode 100644 doc/SiteUrl.twp create mode 100644 doc/_root.twp create mode 100644 doc/authftp.twp create mode 100644 doc/caturl.twp create mode 100644 doc/compileAndGo.twp create mode 100644 doc/fconv.twp create mode 100644 doc/fnconv.twp create mode 100644 doc/geturl.twp create mode 100644 doc/mkRewriteRules.twp create mode 100644 doc/mkiso.twp create mode 100644 doc/mkurl.twp create mode 100644 doc/mkusfx.twp create mode 100644 doc/openurl.twp create mode 100644 doc/rmtildes.twp create mode 100644 doc/rruns.twp create mode 100644 doc/ruinst.twp create mode 100644 doc/runs.twp create mode 100644 doc/rwoinst.twp create mode 100644 doc/twsync.twp create mode 100644 doc/ubackup.twp create mode 100644 doc/uconf.twp create mode 100644 doc/ucrontab.twp create mode 100644 doc/udir.twp create mode 100644 doc/uenv.twp create mode 100644 doc/uinc.sh.twp create mode 100644 doc/uinc.twp create mode 100644 doc/uinst.sh.twp create mode 100644 doc/uinst.twp create mode 100644 doc/ujava.twp create mode 100644 doc/uldap.twp create mode 100644 doc/ulib.twp create mode 100644 doc/ulib_DEFAULTS.twp create mode 100644 doc/ulib_apache.twp create mode 100644 doc/ulib_base.twp create mode 100644 doc/ulib_bash.twp create mode 100644 doc/ulib_compat.twp create mode 100644 doc/ulib_conf.twp create mode 100644 doc/ulib_crontab.twp create mode 100644 doc/ulib_debian.twp create mode 100644 doc/ulib_install.twp create mode 100644 doc/ulib_ipcalc.twp create mode 100644 doc/ulib_java.twp create mode 100644 doc/ulib_javaproperties.twp create mode 100644 doc/ulib_ldap.twp create mode 100644 doc/ulib_ldif.twp create mode 100644 doc/ulib_legacy.twp create mode 100644 doc/ulib_macosx.twp create mode 100644 doc/ulib_mkcrypt.twp create mode 100644 doc/ulib_modeline.twp create mode 100644 doc/ulib_network-manager-service.twp create mode 100644 doc/ulib_pkg.twp create mode 100644 doc/ulib_prefixes.twp create mode 100644 doc/ulib_pretty.twp create mode 100644 doc/ulib_runs.twp create mode 100644 doc/ulib_service.twp create mode 100644 doc/ulib_sysinfos.twp create mode 100644 doc/ulib_tiddlywiki.twp create mode 100644 doc/ulib_udir.twp create mode 100644 doc/ulib_uenv.twp create mode 100644 doc/ulib_uenv_update.twp create mode 100644 doc/ulib_uinc.twp create mode 100644 doc/ulib_uinst.twp create mode 100644 doc/ulib_ulib.twp create mode 100644 doc/ulib_ulibsh.twp create mode 100644 doc/ulib_vcs.twp create mode 100644 doc/ulib_virsh.twp create mode 100644 doc/ulib_webobjects.twp create mode 100644 doc/ulib_woinst.twp create mode 100644 doc/ulib_wondermonitor.twp create mode 100644 doc/ulib_wosign.twp create mode 100644 doc/ulib_wotaskd.twp create mode 100644 doc/ulibshell.twp create mode 100644 doc/ulibsync.twp create mode 100644 doc/umatch.twp create mode 100644 doc/umirror.twp create mode 100755 doc/update.sh create mode 100644 doc/uprefix.twp create mode 100644 doc/uproject.twp create mode 100644 doc/usysinfos.twp create mode 100644 doc/vzusage.twp create mode 100644 doc/woArchive.twp create mode 100644 doc/woSwitch.twp create mode 100644 doc/woctl.twp create mode 100644 doc/woinst.twp create mode 100644 doc/wosign.twp create mode 100755 dokuwiki create mode 100755 fconv create mode 100755 fnconv create mode 100755 geturl create mode 100644 legacy/.udir create mode 100644 legacy/instinc/base create mode 100644 legacy/instinc/envsetup create mode 100644 legacy/instinc/envsetup_update create mode 100644 legacy/instinc/httpd_profile create mode 100644 legacy/instinc/prefixes create mode 100644 legacy/instinc/uinstdir create mode 100644 legacy/instinc/wobase create mode 100644 legacy/instinc/woconf create mode 100644 legacy/instinc/wofunctions create mode 100644 legacy/instinc/womonitor create mode 100644 legacy/instinc/worestart create mode 100644 legacy/instinc/wosign create mode 100644 legacy/instinc/wotag create mode 100644 legacy/sysinc/base create mode 100644 legacy/sysinc/basevars create mode 100644 legacy/sysinc/begingetopt create mode 100644 legacy/sysinc/begingetopt-- create mode 100644 legacy/sysinc/begingetopt--_help create mode 100644 legacy/sysinc/begingetopt_help create mode 100644 legacy/sysinc/crontab create mode 100644 legacy/sysinc/endgetopt create mode 100644 legacy/sysinc/endgetopt-- create mode 100644 legacy/sysinc/ewaitcmd.test create mode 100644 legacy/sysinc/functions create mode 100644 legacy/sysinc/java create mode 100644 legacy/sysinc/private/init create mode 100644 legacy/sysinc/scripts create mode 100644 legacy/sysinc/system_caps create mode 100644 legacy/sysinc/usebash create mode 100644 legacy/sysinc/utools create mode 100644 legacy/twinc/TODO.html create mode 100644 legacy/twinc/TODO.js create mode 100644 legacy/twinc/base.js create mode 100644 legacy/twinc/baseui.js create mode 100644 legacy/twinc/old.js create mode 100644 legacy/twinc/patches.js create mode 100644 legacy/twinc/tiddlywiki create mode 100644 legacy/vbsinc/base create mode 100644 legacy/vbsinc/java create mode 100755 lib/b36sha1.php create mode 100755 lib/b36sha1.py create mode 100644 lib/backup.commands.template create mode 100644 lib/backup.excludes.template create mode 100644 lib/backup.includes.template create mode 100755 lib/backup.mount create mode 100755 lib/backup.umount create mode 100644 lib/bashrc.d/color_ls.[Darwin] create mode 100644 lib/bashrc.d/color_ls.[Linux] create mode 100644 lib/bashrc.d/color_ls.[SunOS] create mode 100644 lib/bashrc.d/confirm_rm,mv,cp create mode 100644 lib/bashrc.d/ls_options create mode 100644 lib/bashrc.d/nutools.userconf create mode 100644 lib/default/authftp create mode 100644 lib/default/dokuwiki create mode 100644 lib/default/mediawiki create mode 100644 lib/default/proxy create mode 100644 lib/default/pubkeys create mode 100644 lib/default/runs create mode 100644 lib/default/ubackup create mode 100644 lib/default/uldap create mode 100755 lib/init.d/install-kvm-stop-all create mode 100755 lib/init.d/install-openvz-fix-etchosts create mode 100755 lib/init.d/kvm-stop-all create mode 100755 lib/init.d/openvz-fix-etchosts create mode 100644 lib/makeself-2.1.5/COPYING create mode 100644 lib/makeself-2.1.5/README create mode 100644 lib/makeself-2.1.5/TODO create mode 100755 lib/makeself-2.1.5/makeself-header.sh create mode 100755 lib/makeself-2.1.5/makeself-header.sh.orig create mode 100644 lib/makeself-2.1.5/makeself.1 create mode 100644 lib/makeself-2.1.5/makeself.lsm create mode 100755 lib/makeself-2.1.5/makeself.sh create mode 100755 lib/makeself-2.1.5/makeself.sh.orig create mode 100644 lib/makeself.url create mode 100755 lib/mkselfinst create mode 100644 lib/nutoolsrc create mode 100644 lib/profile.d/bash_prompt create mode 100644 lib/profile.d/bin_in_path create mode 100644 lib/profile.d/dbus create mode 100644 lib/profile.d/histcontrol create mode 100644 lib/profile.d/local_bin_in_path.[Darwin] create mode 100644 lib/profile.d/nutools create mode 100644 lib/profile.d/nutools.userconf create mode 100644 lib/profile.d/proxy create mode 100644 lib/profile.d/runs.userconf create mode 100644 lib/profile.d/webobjects create mode 100644 lib/profile.d/webobjects.userconf create mode 100755 lib/pywrapper create mode 100644 lib/reptyr/.gitignore create mode 100644 lib/reptyr/COPYING create mode 100644 lib/reptyr/ChangeLog create mode 100644 lib/reptyr/Makefile create mode 100644 lib/reptyr/NOTES create mode 100644 lib/reptyr/README.md create mode 100644 lib/reptyr/arch/amd64.h create mode 100644 lib/reptyr/arch/arm.h create mode 100644 lib/reptyr/arch/default-syscalls.h create mode 100644 lib/reptyr/arch/i386.h create mode 100644 lib/reptyr/arch/x86_common.h create mode 100644 lib/reptyr/attach.c create mode 100644 lib/reptyr/ptrace.c create mode 100644 lib/reptyr/ptrace.h create mode 100644 lib/reptyr/reptyr.1 create mode 100644 lib/reptyr/reptyr.c create mode 100644 lib/reptyr/reptyr.fr.1 create mode 100644 lib/reptyr/reptyr.h create mode 100644 lib/reptyr/reptyr.patch create mode 100755 lib/template create mode 100755 lib/templates/auto create mode 100755 lib/templates/java create mode 100644 lib/templates/script.template create mode 100755 lib/templates/shell create mode 100644 lib/templates/templates.conf create mode 100644 lib/tiddlywiki/TiddlySaver.jar create mode 100755 lib/tiddlywiki/download-latest.sh create mode 100644 lib/tiddlywiki/empty.html create mode 100644 lib/tiddlywiki/tiddlywiki.url create mode 100644 lib/uinst/conf create mode 100644 lib/uinst/rootconf create mode 100644 lib/uinst/system_caps.legacy create mode 100755 mediawiki create mode 100755 mkRewriteRules create mode 100755 mkiso create mode 100755 mkurl create mode 100755 mkusfx create mode 100755 mocifs create mode 100755 modav create mode 100755 moiso create mode 100755 mossh create mode 100755 mysqlcsv create mode 100755 mysqlloadcsv create mode 100755 openurl create mode 100644 profile create mode 100644 pyulib/.project create mode 100644 pyulib/.pydevproject create mode 100644 pyulib/.settings/org.eclipse.core.resources.prefs create mode 100644 pyulib/.settings/org.eclipse.core.runtime.prefs create mode 100644 pyulib/.udir create mode 100644 pyulib/MANIFEST.in create mode 100644 pyulib/devel/TODO.txt create mode 100644 pyulib/devel/Tasks.txt create mode 100755 pyulib/devel/doctests create mode 100755 pyulib/devel/idleshell create mode 100755 pyulib/devel/mkdiff-webpy.sh create mode 100755 pyulib/devel/pt create mode 100755 pyulib/devel/pyshell create mode 100644 pyulib/devel/python_interpreters create mode 100644 pyulib/devel/shell_init create mode 100755 pyulib/devel/spp create mode 100644 pyulib/devel/web.py-0.33.patch create mode 100644 pyulib/migrate/base.py create mode 100644 pyulib/migrate/tasks1/TODO.py create mode 100755 pyulib/migrate/tasks1/TODO_helper.py create mode 100755 pyulib/migrate/tasks1/tiddlywiki.py create mode 100644 pyulib/migrate/tasks1/tiddlywiki2.py create mode 100755 pyulib/migrate/tasks2/Tasks.py create mode 100755 pyulib/migrate/tasks2/TiddlyWiki.py create mode 100644 pyulib/migrate/wo.py create mode 100755 pyulib/setup.py create mode 100644 pyulib/src/ULIB_CONFIG.py create mode 100644 pyulib/src/ULIB_CONFIG.pyc create mode 100644 pyulib/src/i_need_py23.py create mode 100644 pyulib/src/i_need_py23.pyc create mode 100644 pyulib/src/i_need_py24.py create mode 100644 pyulib/src/i_need_py25.py create mode 100644 pyulib/src/i_need_py26.py create mode 100644 pyulib/src/uapps/__init__.py create mode 100644 pyulib/src/uapps/jclain_license.txt create mode 100755 pyulib/src/uapps/plbck.py create mode 100755 pyulib/src/uapps/plver.py create mode 100755 pyulib/src/uapps/pyucontacts.py create mode 100755 pyulib/src/uapps/pyurelease.py create mode 100755 pyulib/src/uapps/pyutasks.py create mode 100755 pyulib/src/uapps/pyuupdate_inc.py create mode 100644 pyulib/src/uapps/tasks/__init__.py create mode 100644 pyulib/src/uapps/tasks/httpd/__init__.py create mode 100644 pyulib/src/uapps/tasks/httpd/server.py create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/ie.css create mode 100755 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/icons/cross.png create mode 100755 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/icons/key.png create mode 100755 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/icons/tick.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/readme.txt create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/screen.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/fancy-type/readme.txt create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/fancy-type/screen.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/doc.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/email.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/external.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/feed.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/im.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/pdf.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/visited.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/xls.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/readme.txt create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/screen.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/rtl/readme.txt create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/rtl/screen.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/AUTHORS.textile create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/README.textile create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/screen.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/print.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/screen.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/blueprint/src/grid.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/bpsuppl.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock-samples.html create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/add.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/base.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/delete.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/edit.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/error.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/go.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/link.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/pause.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/play.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/red.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/start.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/clock/stop.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/edit.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/favicon.ico create mode 100644 pyulib/src/uapps/tasks/httpd/static/index.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/index.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/index.min.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/jquery.hoverIntent.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/jquery.hoverIntent.min.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/jquery.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/jquery.min.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/jquery.tooltip.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/jquery.tooltip.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/jquery.tooltip.min.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/taskshttpd.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/taskshttpd.min.js create mode 100644 pyulib/src/uapps/tasks/httpd/static/zoom-samples.html create mode 100644 pyulib/src/uapps/tasks/httpd/static/zoom.css create mode 100644 pyulib/src/uapps/tasks/httpd/static/zoom/base.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/zoom/in.png create mode 100644 pyulib/src/uapps/tasks/httpd/static/zoom/out.png create mode 100644 pyulib/src/uapps/tasks/httpd/templates/edit.html create mode 100644 pyulib/src/uapps/tasks/httpd/templates/index.html create mode 100644 pyulib/src/uapps/tasks/httpd/templates/task.htmlc create mode 100644 pyulib/src/uapps/tasks/httpstore/__init__.py create mode 100644 pyulib/src/uapps/tasks/httpstore/server.py create mode 100644 pyulib/src/uapps/tasks/httpstore/templates/index.html create mode 100644 pyulib/src/uapps/tasks/itfctl.py create mode 100644 pyulib/src/uapps/tasks/taskscli.py create mode 100755 pyulib/src/uapps/uencdetect.py create mode 100755 pyulib/src/uapps/uproject.py create mode 100644 pyulib/src/uapps/ur_license.txt create mode 100755 pyulib/src/uapps/urandomize.py create mode 100644 pyulib/src/ulib/__init__.py create mode 100644 pyulib/src/ulib/__init__.pyc create mode 100644 pyulib/src/ulib/all/__init__.py create mode 100644 pyulib/src/ulib/all/__init__.pyc create mode 100644 pyulib/src/ulib/base/__init__.py create mode 100644 pyulib/src/ulib/base/__init__.pyc create mode 100644 pyulib/src/ulib/base/args.py create mode 100644 pyulib/src/ulib/base/args.pyc create mode 100644 pyulib/src/ulib/base/base.py create mode 100644 pyulib/src/ulib/base/base.pyc create mode 100644 pyulib/src/ulib/base/config.py create mode 100644 pyulib/src/ulib/base/config.pyc create mode 100644 pyulib/src/ulib/base/control.py create mode 100644 pyulib/src/ulib/base/control.pyc create mode 100644 pyulib/src/ulib/base/dates.py create mode 100644 pyulib/src/ulib/base/dates.pyc create mode 100644 pyulib/src/ulib/base/editor.py create mode 100644 pyulib/src/ulib/base/editor.pyc create mode 100644 pyulib/src/ulib/base/encdetect.py create mode 100644 pyulib/src/ulib/base/encdetect.pyc create mode 100644 pyulib/src/ulib/base/encoding.py create mode 100644 pyulib/src/ulib/base/encoding.pyc create mode 100644 pyulib/src/ulib/base/env.py create mode 100644 pyulib/src/ulib/base/env.pyc create mode 100644 pyulib/src/ulib/base/files.py create mode 100644 pyulib/src/ulib/base/files.pyc create mode 100644 pyulib/src/ulib/base/flock.py create mode 100644 pyulib/src/ulib/base/flock.pyc create mode 100644 pyulib/src/ulib/base/functions.py create mode 100644 pyulib/src/ulib/base/functions.pyc create mode 100755 pyulib/src/ulib/base/htmlentities.py create mode 100644 pyulib/src/ulib/base/htmlentities.pyc create mode 100644 pyulib/src/ulib/base/i_need_py23.py create mode 100644 pyulib/src/ulib/base/i_need_py23.pyc create mode 100644 pyulib/src/ulib/base/i_need_py24.py create mode 100644 pyulib/src/ulib/base/i_need_py24.pyc create mode 100644 pyulib/src/ulib/base/i_need_py25.py create mode 100644 pyulib/src/ulib/base/i_need_py26.py create mode 100644 pyulib/src/ulib/base/input.py create mode 100644 pyulib/src/ulib/base/input.pyc create mode 100644 pyulib/src/ulib/base/lines.py create mode 100644 pyulib/src/ulib/base/lines.pyc create mode 100644 pyulib/src/ulib/base/output.py create mode 100644 pyulib/src/ulib/base/output.pyc create mode 100644 pyulib/src/ulib/base/pager.py create mode 100644 pyulib/src/ulib/base/pager.pyc create mode 100644 pyulib/src/ulib/base/paths.py create mode 100644 pyulib/src/ulib/base/paths.pyc create mode 100644 pyulib/src/ulib/base/procs.py create mode 100644 pyulib/src/ulib/base/procs.pyc create mode 100644 pyulib/src/ulib/base/pversion.py create mode 100644 pyulib/src/ulib/base/pversion.pyc create mode 100644 pyulib/src/ulib/base/times.py create mode 100644 pyulib/src/ulib/base/times.pyc create mode 100644 pyulib/src/ulib/base/tmpfiles.py create mode 100644 pyulib/src/ulib/base/tmpfiles.pyc create mode 100644 pyulib/src/ulib/base/uio.py create mode 100644 pyulib/src/ulib/base/uio.pyc create mode 100644 pyulib/src/ulib/base/words.py create mode 100644 pyulib/src/ulib/base/words.pyc create mode 100644 pyulib/src/ulib/ext/__init__.py create mode 100644 pyulib/src/ulib/ext/__init__.pyc create mode 100644 pyulib/src/ulib/ext/optik141/README.txt create mode 100644 pyulib/src/ulib/ext/optik141/__init__.py create mode 100644 pyulib/src/ulib/ext/optik141/errors.py create mode 100644 pyulib/src/ulib/ext/optik141/help.py create mode 100644 pyulib/src/ulib/ext/optik141/option.py create mode 100644 pyulib/src/ulib/ext/optik141/option_parser.py create mode 100644 pyulib/src/ulib/ext/optik141/textwrap.py create mode 100644 pyulib/src/ulib/ext/simplejson/__init__.py create mode 100644 pyulib/src/ulib/ext/simplejson/__init__.pyc create mode 100644 pyulib/src/ulib/ext/simplejson/_speedups.c create mode 100644 pyulib/src/ulib/ext/simplejson/decoder.py create mode 100644 pyulib/src/ulib/ext/simplejson/decoder.pyc create mode 100644 pyulib/src/ulib/ext/simplejson/encoder.py create mode 100644 pyulib/src/ulib/ext/simplejson/encoder.pyc create mode 100644 pyulib/src/ulib/ext/simplejson/scanner.py create mode 100644 pyulib/src/ulib/ext/simplejson/scanner.pyc create mode 100644 pyulib/src/ulib/ext/tarfile/README.txt create mode 100644 pyulib/src/ulib/ext/tarfile/__init__.py create mode 100644 pyulib/src/ulib/ext/tarfile/gzip22.py create mode 100644 pyulib/src/ulib/ext/tarfile/tarfile.py create mode 100644 pyulib/src/ulib/ext/web/__init__.py create mode 100644 pyulib/src/ulib/ext/web/__init__.pyc create mode 100755 pyulib/src/ulib/ext/web/application.py create mode 100644 pyulib/src/ulib/ext/web/application.pyc create mode 100644 pyulib/src/ulib/ext/web/browser.py create mode 100644 pyulib/src/ulib/ext/web/browser.pyc create mode 100644 pyulib/src/ulib/ext/web/contrib/__init__.py create mode 100644 pyulib/src/ulib/ext/web/contrib/template.py create mode 100644 pyulib/src/ulib/ext/web/db.py create mode 100644 pyulib/src/ulib/ext/web/db.pyc create mode 100644 pyulib/src/ulib/ext/web/debugerror.py create mode 100644 pyulib/src/ulib/ext/web/debugerror.pyc create mode 100644 pyulib/src/ulib/ext/web/form.py create mode 100644 pyulib/src/ulib/ext/web/form.pyc create mode 100644 pyulib/src/ulib/ext/web/http.py create mode 100644 pyulib/src/ulib/ext/web/http.pyc create mode 100644 pyulib/src/ulib/ext/web/httpserver.py create mode 100644 pyulib/src/ulib/ext/web/httpserver.pyc create mode 100644 pyulib/src/ulib/ext/web/net.py create mode 100644 pyulib/src/ulib/ext/web/net.pyc create mode 100644 pyulib/src/ulib/ext/web/session.py create mode 100644 pyulib/src/ulib/ext/web/session.pyc create mode 100644 pyulib/src/ulib/ext/web/template.py create mode 100644 pyulib/src/ulib/ext/web/template.pyc create mode 100644 pyulib/src/ulib/ext/web/test.py create mode 100755 pyulib/src/ulib/ext/web/utils.py create mode 100644 pyulib/src/ulib/ext/web/utils.pyc create mode 100644 pyulib/src/ulib/ext/web/webapi.py create mode 100644 pyulib/src/ulib/ext/web/webapi.pyc create mode 100644 pyulib/src/ulib/ext/web/webopenid.py create mode 100644 pyulib/src/ulib/ext/web/webopenid.pyc create mode 100644 pyulib/src/ulib/ext/web/wsgi.py create mode 100644 pyulib/src/ulib/ext/web/wsgi.pyc create mode 100644 pyulib/src/ulib/ext/web/wsgiserver/LICENSE.txt create mode 100644 pyulib/src/ulib/ext/web/wsgiserver/__init__.py create mode 100644 pyulib/src/ulib/formats/__init__.py create mode 100644 pyulib/src/ulib/formats/__init__.pyc create mode 100644 pyulib/src/ulib/formats/base.py create mode 100644 pyulib/src/ulib/formats/base.pyc create mode 100644 pyulib/src/ulib/formats/boolean.py create mode 100644 pyulib/src/ulib/formats/boolean.pyc create mode 100644 pyulib/src/ulib/formats/datetime.py create mode 100644 pyulib/src/ulib/formats/datetime.pyc create mode 100644 pyulib/src/ulib/formats/float.py create mode 100644 pyulib/src/ulib/formats/float.pyc create mode 100644 pyulib/src/ulib/formats/integer.py create mode 100644 pyulib/src/ulib/formats/integer.pyc create mode 100644 pyulib/src/ulib/formats/jour.py create mode 100644 pyulib/src/ulib/formats/jour.pyc create mode 100644 pyulib/src/ulib/formats/strings.py create mode 100644 pyulib/src/ulib/formats/strings.pyc create mode 100644 pyulib/src/ulib/formats/telephone.py create mode 100644 pyulib/src/ulib/formats/telephone.pyc create mode 100644 pyulib/src/ulib/formats/weekday.py create mode 100644 pyulib/src/ulib/formats/weekday.pyc create mode 100644 pyulib/src/ulib/gae/__init__.py create mode 100644 pyulib/src/ulib/gae/dao.py create mode 100644 pyulib/src/ulib/gae/datetime.py create mode 100644 pyulib/src/ulib/gae/pages.py create mode 100644 pyulib/src/ulib/json/__init__.py create mode 100644 pyulib/src/ulib/json/__init__.pyc create mode 100644 pyulib/src/ulib/optparse/__init__.py create mode 100644 pyulib/src/ulib/optparse/__init__.pyc create mode 100644 pyulib/src/ulib/p/__init__.py create mode 100644 pyulib/src/ulib/p/__init__.pyc create mode 100644 pyulib/src/ulib/p/dirconf.py create mode 100644 pyulib/src/ulib/p/dirconf.pyc create mode 100644 pyulib/src/ulib/p/templ/__init__.py create mode 100644 pyulib/src/ulib/p/templ/base.py create mode 100644 pyulib/src/ulib/p/templ/htmltempl.py create mode 100644 pyulib/src/ulib/p/templ/markdowntempl.py create mode 100644 pyulib/src/ulib/p/templ/pytempl.py create mode 100644 pyulib/src/ulib/p/templ/shtempl.py create mode 100644 pyulib/src/ulib/p/templ/texttempl.py create mode 100644 pyulib/src/ulib/p/uinc/__init__.py create mode 100644 pyulib/src/ulib/p/uinc/__init__.pyc create mode 100644 pyulib/src/ulib/p/uinc/output.py create mode 100644 pyulib/src/ulib/p/uinc/output.pyc create mode 100644 pyulib/src/ulib/p/uinc/spec.py create mode 100644 pyulib/src/ulib/p/uinc/spec.pyc create mode 100644 pyulib/src/ulib/p/uinc/updater.py create mode 100644 pyulib/src/ulib/p/uinc/updater.pyc create mode 100644 pyulib/src/ulib/p/vcs/__init__.py create mode 100644 pyulib/src/ulib/p/vcs/base.py create mode 100644 pyulib/src/ulib/p/vcs/cvs.py create mode 100644 pyulib/src/ulib/p/vcs/svn.py create mode 100644 pyulib/src/ulib/p/wop/__init__.py create mode 100644 pyulib/src/ulib/p/wop/uinst.py create mode 100644 pyulib/src/ulib/sa/__init__.py create mode 100644 pyulib/src/ulib/sa/dao.py create mode 100644 pyulib/src/ulib/sa/datetime.py create mode 100644 pyulib/src/ulib/sa/props.py create mode 100644 pyulib/src/ulib/tasks/__init__.py create mode 100644 pyulib/src/ulib/tasks/base.py create mode 100644 pyulib/src/ulib/tasks/consts.py create mode 100644 pyulib/src/ulib/tasks/factories.py create mode 100644 pyulib/src/ulib/tasks/httpclient.py create mode 100644 pyulib/src/ulib/tasks/multiple.py create mode 100644 pyulib/src/ulib/tasks/projects.py create mode 100644 pyulib/src/ulib/tasks/simple.py create mode 100644 pyulib/src/ulib/tasks/store.py create mode 100644 pyulib/src/ulib/tasks/tasks.py create mode 100644 pyulib/src/ulib/tasks/tasksctl.py create mode 100644 pyulib/src/ulib/tasks/tasksrc.py create mode 100644 pyulib/src/ulib/templ/__init__.py create mode 100644 pyulib/src/ulib/templ/base.py create mode 100644 pyulib/src/ulib/templ/htmltempl.py create mode 100644 pyulib/src/ulib/templ/javatempl.py create mode 100644 pyulib/src/ulib/templ/pytempl.py create mode 100644 pyulib/src/ulib/templ/wikitempl.py create mode 100644 pyulib/src/ulib/templ/wotempl.py create mode 100644 pyulib/src/ulib/web/__init__.py create mode 100644 pyulib/src/ulib/web/__init__.pyc create mode 100644 pyulib/src/ulib/web/api.py create mode 100644 pyulib/src/ulib/web/api.pyc create mode 100644 pyulib/src/ulib/web/pages.py create mode 100644 pyulib/src/ulib/web/pages.pyc create mode 100644 pyulib/test/Contacts.txt create mode 100644 pyulib/test/base.py create mode 100755 pyulib/test/run_tests create mode 100755 pyulib/test/template.py create mode 100644 pyulib/test/test_all.py create mode 100644 pyulib/test/test_base/__init__.py create mode 100644 pyulib/test/test_base/base.py create mode 100644 pyulib/test/test_base/test.config create mode 100644 pyulib/test/test_base/test.sh_config create mode 100755 pyulib/test/test_base/test_args.py create mode 100755 pyulib/test/test_base/test_base.py create mode 100755 pyulib/test/test_base/test_config.py create mode 100755 pyulib/test/test_base/test_control.py create mode 100755 pyulib/test/test_base/test_dates.py create mode 100755 pyulib/test/test_base/test_editor.py create mode 100755 pyulib/test/test_base/test_encdetect.py create mode 100755 pyulib/test/test_base/test_encoding.py create mode 100755 pyulib/test/test_base/test_env.py create mode 100755 pyulib/test/test_base/test_files.py create mode 100755 pyulib/test/test_base/test_flock.py create mode 100755 pyulib/test/test_base/test_functions.py create mode 100755 pyulib/test/test_base/test_input.py create mode 100755 pyulib/test/test_base/test_iso8859.py create mode 100755 pyulib/test/test_base/test_lines.py create mode 100755 pyulib/test/test_base/test_output.py create mode 100755 pyulib/test/test_base/test_pager.py create mode 100755 pyulib/test/test_base/test_paths.py create mode 100755 pyulib/test/test_base/test_procs.py create mode 100755 pyulib/test/test_base/test_times.py create mode 100755 pyulib/test/test_base/test_tmpfiles.py create mode 100755 pyulib/test/test_base/test_uio.py create mode 100755 pyulib/test/test_base/test_words.py create mode 100755 reptyr.cgo create mode 100755 rmtildes create mode 100755 rruns create mode 100755 ruinst create mode 100755 runs create mode 100755 rwoinst create mode 100755 twsync create mode 100755 uawk create mode 100755 ubackup create mode 100755 ucalc create mode 100755 uconf create mode 100755 ucrontab create mode 100755 udaemon.cgo create mode 100755 udir create mode 100755 udist create mode 100755 uenv create mode 100755 uinc create mode 100755 uinc.sh create mode 100755 uinst create mode 100755 uinst.sh create mode 100755 ujava create mode 100755 uldap create mode 100644 ulib/DEFAULTS create mode 100644 ulib/PREFIXES-DEFAULTS create mode 100644 ulib/apache create mode 100644 ulib/auto create mode 100644 ulib/awk create mode 100644 ulib/base create mode 100644 ulib/bash create mode 100644 ulib/compat create mode 100644 ulib/conf create mode 100644 ulib/crontab create mode 100644 ulib/debian create mode 100644 ulib/install create mode 100644 ulib/ipcalc create mode 100644 ulib/java create mode 100644 ulib/javaproperties create mode 100644 ulib/json create mode 100644 ulib/ldap create mode 100644 ulib/ldif create mode 100644 ulib/legacy create mode 100644 ulib/macosx create mode 100644 ulib/mkcrypt create mode 100644 ulib/modeline create mode 100644 ulib/network-manager-service create mode 100644 ulib/nutools/.udir create mode 100644 ulib/nutools/plbck create mode 100644 ulib/nutools/plver create mode 100644 ulib/nutools/pyulib create mode 100644 ulib/nutools/uencdetect create mode 100644 ulib/prefixes create mode 100644 ulib/pretty create mode 100644 ulib/redhat create mode 100644 ulib/runs create mode 100644 ulib/service create mode 100755 ulib/support/install-pubkeys.sh create mode 100755 ulib/support/mkcrypt.py create mode 100755 ulib/support/uinc.py create mode 100755 ulib/support/uinst2s create mode 100755 ulib/support/uinst2s_python create mode 100755 ulib/support/woinst2s create mode 100644 ulib/sysinfos create mode 100644 ulib/tiddlywiki create mode 100644 ulib/udir create mode 100644 ulib/uenv create mode 100644 ulib/uenv_update create mode 100644 ulib/uinc create mode 100644 ulib/uinst create mode 100644 ulib/ulib create mode 100644 ulib/ulibsh create mode 100644 ulib/vcs create mode 100644 ulib/virsh create mode 100644 ulib/webobjects create mode 100644 ulib/woinst create mode 100644 ulib/wondermonitor create mode 100644 ulib/wosign create mode 100644 ulib/wotaskd create mode 100755 ulibshell create mode 100755 ulibsync create mode 100755 umatch create mode 100755 umirror create mode 100755 uprefix create mode 100755 uproject create mode 100755 urunserial create mode 100755 ussh create mode 100755 usysinfos create mode 100755 utempl create mode 100755 utrigger create mode 100755 vzusage create mode 100755 woArchive create mode 100755 woSwitch create mode 100755 woctl create mode 100755 wofixsql.py create mode 100755 woinst create mode 100755 wosign diff --git a/.dokuwikigen b/.dokuwikigen new file mode 100644 index 0000000..7c79edd --- /dev/null +++ b/.dokuwikigen @@ -0,0 +1,60 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +gendefault + +etitle "Documentation de ulib" +array_from_lines ulibnames "$(list_files ulib)" +# faire les pages +for ulibname in "${ulibnames[@]}"; do + awkrun <"ulib/$ulibname" 'BEGIN { + in_func = 0 + dump_doc = 0 + dumped_doc = 0 + print +} +!in_func && $0 ~ /^function / { + if (match($0, /function +([^ ]+)\(\)/, vs)) { + funcname = vs[1] + if (funcname !~ /^_/) { + in_func = 1 + dump_doc = 1 + dumped_doc = 0 + print "==== " funcname " ====" + if ($0 ~ /}$/) { + in_func = 0 + dump_doc = 0 + dumped_doc = 0 + } + next + } + } +} +in_func && dump_doc && $0 !~ /^ *#/ { + dump_doc = 0 +} +in_func && dump_doc && $0 ~ /^ *#/ { + if (!dumped_doc) print "" + gsub(/^ *#+/, "") + print + dumped_doc = 1 +} +in_func && $0 ~ /}$/ { + if (dumped_doc) print "" + in_func = 0 + dump_doc = 0 + dumped_doc = 0 +} +END { if (in_func) print "" } +' | setpage "$ulibname" ulib +done +eend + +# faire l'entête +addpage start < /proc/cpuinfo; then + module=kvm_intel + elif grep -q ^flags.*\\\ /proc/cpuinfo; then + module=kvm_amd + else + module= + fi + if [ -n "$module" ]; then + for module in kvm $module; do + if ! lsmod | quietgrep "$module"; then + eecho "+$module" + modprobe "$module" || return 1 + modified=1 + fi + done + fi + + [ -n "$modified" ] && sleep 1 + + service= + if check_sysinfos -d redhatlike; then + for i in libvirtd libvirtd-bin; do + [ -f "/etc/init.d/$i" ] && { + service="$i" + break + } + done + elif check_sysinfos -d debianlike; then + service=libvirt-bin + fi + if [ -n "$service" ]; then + # s'assurer que le service tourne + service libvirt-bin startm + else + return 0 + fi +} + +function ensure_virtualbox() { + # S'assurer que les modules kvm sont déchargés, que les modules VirtualBox + # sont chargés, et que le service vboxdrv est démarré + local module modified + + # Vérifier que kvm{,_intel,_amd} ne sont pas chargés + for module in kvm_intel kvm_amd kvm; do + if lsmod | quietgrep "$module"; then + eecho "-$module" + rmmod "$module" || return 1 + modified=1 + fi + done + + # Vérifier que les modules vbox* sont chargés + [ -f /lib/modules/`uname -r`/updates/dkms/vboxpci.ko ] && + vboxmodules=(vboxnetflt vboxnetadp vboxpci vboxdrv) || + vboxmodules=(vboxnetflt vboxnetadp vboxdrv) + for module in "${vboxmodules[@]}"; do + if ! lsmod | quietgrep "$module"; then + eecho "+$module" + modprobe "$module" || return 1 + modified=1 + fi + done + + [ -n "$modified" ] && sleep 1 + + # s'assurer que le service tourne + service vboxdrv startm +} + +parse_opts + "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +run_as_root "$@" + +case "${1:-kvm}" in +k|kvm) ensure_kvm;; +v|vbox|virtualbox) ensure_virtualbox;; +esac diff --git a/SKvm b/SKvm new file mode 100755 index 0000000..43e857d --- /dev/null +++ b/SKvm @@ -0,0 +1,189 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: lancer une machine virtuelle kvm + +USAGE + $scriptname [options] vmName + $scriptname {-l|-A|-g} + +OPTIONS + -n Ne rien faire excepté s'assurer que les modules kvm sont chargés + -u Lancer l'opération avec les droits de l'utilisateur courant. Par défaut, + ce script tente d'acquérir les droits de root. + -l Lister les machines virtuelles + -s Démarrer la machine virtuelle (par défaut) + Si le nom de la machine virtuelle n'est pas spécifiée, un menu est + affiché + -k Arrêter la machine virtuelle + -H Arrêter sauvagement la machine virtuelle + -r Redémarrer la machine virtuelle + -S Enregistrer l'état de la machine virtuelle + -A Arrêter toutes les machines virtuelles qui tournent actuellement + -g Afficher le gestionnaire de machines virtuelle" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS virsh || +exit 1 + +function build_arrays() { + # Construire les listes avms, rvms, svms + # rvms: les vms qui tournent + # avms: toutes les vms + # svms: les vms qui sont arrêtées + rvms=() + eval "$(virsh_list | awk '{print $2}' | sort | sed 's/^/array_add rvms /g')" + avms=() + eval "$(virsh_list --all | awk '{print $2}' | sort | sed 's/^/array_add avms /g')" + svms=() + for avm in "${avms[@]}"; do + array_contains rvms "$avm" || array_add svms "$avm" + done +} + +function is_numeric() { + [ -n "$1" -a -z "${1//[0-9]/}" ] +} + +function select_vm() { + # Afficher un menu pour sélectionner la machine virtuelle + simple_menu "${2:-vm}" "${1:-avms}" \ + -t "Machines virtuelles" \ + -m "${3:-Choisissez la VM}" +} + +function start_virtmanager() { + # Lancer le gestionnaire de machines virtuelles + # Les mesures prises le sont pour compatibilité avec Fedora, si la commande + # est lancée depuis le menu. En effet, toutes les processus fils sont tués + # s'ils ne sont pas détachés avant la fin de la commande du menu. Le délai + # est donc pour laisser le temps à virt-manager de s'initialiser. + nohup >/dev/null 2>&1 virt-manager & + sleep 3 +} + +sudo=1 +action=start +stopaction=shutdown +parse_opts + "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -u,--user sudo= \ + -n action=nop \ + -l,--list action=list \ + -s,--start action=start \ + -k,-t,--stop '$action=stop; stopaction=shutdown' \ + -H '$action=stop; stopaction=destroy' \ + -r,--restart '$action=stop; stopaction=reboot' \ + -S '$action=stop; stopaction=managedsave' \ + -A,--stopall,--stop-all action=stopall \ + -g action=gui \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +if ! is_root && [ -n "$sudo" ]; then + # reconstruire la ligne de commande + case "$action" in + nop) opt=-n;; + list) opt=-l;; + start) opt=-s;; + stop) + case "$stopaction" in + shutdown) opt=-k;; + destroy) opt=-H;; + reboot) opt=-r;; + managedsave) opt=-S;; + *) opt=-k;; + esac + ;; + stopall) opt=--stopall;; + gui) opt=-g;; + esac + run_as_root "$opt" "$@" +fi + +"$scriptdir/EnsureVM" kvm || die +[ "$action" == "nop" ] && exit 0 + +build_arrays + +vm="$1"; shift +if [ -n "$vm" ]; then + if ! array_contains avms "$vm" && is_numeric "$vm"; then + vm=$(($vm - 1)) + vm="${avms[$vm]}" + enote "Sélection de la VM $vm" + fi +fi + +if [ "$action" == list ]; then + for avm in "${avms[@]}"; do + if array_contains rvms "$avm"; then + avm="$(get_color @ g)$avm [running]$(get_color z)" + fi + echo "$avm$state" + done + +elif [ "$action" == start ]; then + if [ -z "$vm" -a -n "${svms[*]}" ]; then + [ -n "${rvms[*]}" ] && + einfo "Les machines virtuelles suivantes sont déjà démarrées: +$(array_join rvms " +" "" " ")" #" + select_vm svms vm "Choisissez la VM à démarrer" + fi + + if [ -n "$vm" ]; then + virsh start "$vm" "$@" + else + ewarn "Aucune VM à démarrer n'a été trouvée" + fi + +elif [ "$action" == stop ]; then + if [ -z "$vm" -a "${rvms[*]}" ]; then + select_vm rvms vm "Choisissez la VM à arrêter avec la méthode $stopaction" + fi + + if [ -n "$vm" ]; then + virsh "$stopaction" "$vm" + fi + +elif [ "$action" == stopall ]; then + # Arrêter toutes les machines virtuelles. Au bout de 10 minutes maximum, + # forcer l'arrêt de toutes celles qui restent + estep "Arrêt de toutes les machines virtuelles" + for vm in "${rvms[@]}"; do + virsh shutdown "$vm" + done + + # laisser 10 secondes pour l'arrêt initial + estep "Attente de l'arrêt des machines..." + sleep 10 + + # Puis attendre au maximum 10 minutes pour l'arrêt de toutes les machines + max=10 + all_stopped= + while [ $max -gt 0 ]; do + build_arrays + if [ -z "${rvms[*]}" ]; then + all_stopped=1 + break + else + einfo "Il y a encore ${#rvms[*]} machine(s) en fonctionnement. Attente de $max minute(s)..." + sleep 60 + max=$(($max - 1)) + fi + done + + # Puis forcer l'arrêt des machines + if [ -z "$all_stopped" ]; then + build_arrays + eattention "Forçage de l'arrêt des machines ${rvms[*]}" + for vm in "${rvms[@]}"; do + virsh destroy "$vm" + done + fi + +elif [ "$action" == gui ]; then + start_virtmanager +fi diff --git a/SVirtualBox b/SVirtualBox new file mode 100755 index 0000000..f91644e --- /dev/null +++ b/SVirtualBox @@ -0,0 +1,127 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: lancer une machine virtuelle VirtualBox + +USAGE + $scriptname [options] vmName + +OPTIONS + -n Ne rien faire excepté s'assurer que les modules VirtualBox sont chargés + -l Lister les machines virtuelles + -s Démarrer la machine virtuelle (par défaut) + Si le nom de la machine virtuelle n'est pas spécifiée, un menu est + affiché + -k Arrêter la machine virtuelle (par ACPI) + -p Mettre en veille la machine virtuelle (par ACPI) + -H Arrêter sauvagement la machine virtuelle + -R Redémarrer sauvagement la machine virtuelle + -S Enregistrer l'état de la machine virtuelle + -g Afficher le gestionnaire de machines virtuelle" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +function build_arrays() { + # Construire les listes avms, rvms, svms + # rvms: les vms qui tournent + # avms: toutes les vms + # svms: les vms qui sont arrêtées + rvms=() + eval "$(VBoxManage -q list runningvms | sort | sed 's/^/array_add rvms /g')" + avms=() + eval "$(VBoxManage -q list vms | sort | sed 's/^/array_add avms /g')" + svms=() + for avm in "${avms[@]}"; do + array_contains rvms "$avm" || array_add svms "$avm" + done +} + +function is_numeric() { + [ -n "$1" -a -z "${1//[0-9]/}" ] +} + +function select_vm() { + # Afficher un menu pour sélectionner la machine virtuelle + simple_menu "${2:-vm}" "${1:-avms}" \ + -t "Machines virtuelles" \ + -m "${3:-Choisissez la VM}" +} + +function start_virtualbox() { + # Lancer le gestionnaire de machines virtuelles + # Les mesures prises le sont pour compatibilité avec Fedora, si la commande + # est lancée depuis le menu. En effet, toutes les processus fils sont tués + # s'ils ne sont pas détachés avant la fin de la commande du menu. Le délai + # est donc pour laisser le temps à VirtualBox de s'initialiser. + nohup >/dev/null 2>&1 VirtualBox & + sleep 3 +} + +action=start +stopaction=acpipowerbutton +parse_opts + "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -n action=nop \ + -l,--list action=list \ + -s,--start action=start \ + -k,-t,--stop '$action=stop; stopaction=acpipowerbutton' \ + -p,--sleep '$action=stop; stopaction=acpisleepbutton' \ + -H '$action=stop; stopaction=poweroff' \ + -R '$action=stop; stopaction=reset' \ + -S '$action=stop; stopaction=savestate' \ + -g action=gui \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +"$scriptdir/EnsureVM" virtualbox || die +[ "$action" == "nop" ] && exit 0 + +build_arrays + +vm="$1"; shift +if [ -n "$vm" ]; then + if ! array_contains avms "$vm" && is_numeric "$vm"; then + vm=$(($vm - 1)) + vm="${avms[$vm]}" + enote "Sélection de la VM $vm" + fi +fi + +if [ "$action" == list ]; then + for avm in "${avms[@]}"; do + if array_contains rvms "$avm"; then + avm="$(get_color @ g)$avm [running]$(get_color z)" + fi + echo "$avm$state" + done + +elif [ "$action" == start ]; then + if [ -z "$vm" -a -n "${svms[*]}" ]; then + [ -n "${rvms[*]}" ] && + einfo "Les machines virtuelles suivantes sont déjà démarées: +$(array_join rvms " +" "" " ")" #" + select_vm svms vm "Choisissez la VM à démarrer" + fi + + if [ -n "$vm" ]; then + VBoxManage -q startvm "$vm" "$@" + else + ewarn "Aucune VM à démarrer n'a été trouvée" + fi + +elif [ "$action" == stop ]; then + if [ -z "$vm" -a "${rvms[*]}" ]; then + select_vm rvms vm "Choisissez la VM à arrêter avec la méthode $stopaction" + fi + + if [ -n "$vm" ]; then + VBoxManage -q controlvm "$vm" "$stopaction" + fi + +elif [ "$action" == gui ]; then + start_virtualbox +fi diff --git a/_root b/_root new file mode 100755 index 0000000..823ece3 --- /dev/null +++ b/_root @@ -0,0 +1,28 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: devenir l'utilisateur root, avec 'sudo' si possible, ou 'su' si +'sudo' n'est pas installé" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire base || +exit 1 + +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +user="$scriptname" +if [ "${user#_}" != "$user" ]; then + user="${user:1}" +fi + +if is_yes "$UTOOLS_USES_SU" || ! progexists sudo; then + eecho "Entrez le mot de passe de $user" + exec su - "$user" +else + eecho "Si demandé, entrez le mot de passe de $USER" + exec sudo su - "$user" +fi diff --git a/authftp b/authftp new file mode 100755 index 0000000..6e7bb5b --- /dev/null +++ b/authftp @@ -0,0 +1,74 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Se connecter sur un site FTP authentifié +Ce script nécessite ncftp. Il est conçu pour faciliter l'accès à des sites FTP +s'il est requis d'y accéder par un proxy FTP pour les connexion authentifiées. + +USAGE + $scriptname [options] host login password [path] + +OPTIONS + -p, --proxy + -n, --noproxy + Forcer l'utilisation, resp. ne pas utiliser, le proxy FTP (i.e. faire la + connexion directement) + -l, --lftp + Se connecter avec lftp au lieu de ncftp. Le fonctionnement n'est pas + garanti si l'on utilise un proxy FTP. + -o OPTION + Ajouter une option à la commande lancée. Si l'option prend un argument, + il faut doubler l'option -o, e.g. + $scriptname -l -o -e -o 'mirror remote local' host login pass + Dans cet exemple, l'option -e de lftp est utilisée pour faire un miroir + local du répertoire remote. + +note: A cause d'une limitation de lftp, ce n'est pas possible de se connecter +automatiquement avec lftp si le mot de passe contient une virgule. A cause de la +façon dont le proxy ftp est configuré, il n'est pas possible de se connecter +avec un mot de passe qui contient le caractère @" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +AUTHFTP_PROXY_DISABLED=1 # par défaut, ne pas utiliser le proxy +set_defaults proxy +set_defaults authftp + +noproxy="$AUTHFTP_PROXY_DISABLED" +lftp= +options=() +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -p,--proxy noproxy= \ + -n,--noproxy noproxy=1 \ + -l,--lftp lftp=1 \ + -o:,--option: options \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +[ -n "$noproxy" -o -n "$AUTHFTP_PROXY_HOST" ] || die "AUTHFTP_PROXY_HOST doit être défini" + +my_login="$PROXY_LOGIN" +my_password="$PROXY_PASSWORD" + +read_value -i "Entrez le nom de l'hôte" host "$1" +read_value -i "Entrez l'identifiant de connexion" login "$2" +read_value -i "Entrez le mot de passe" password "$3" +read_value -i "Entrez le chemin" path "$4" N + +if [ -n "$lftp" ]; then + if [ -n "$noproxy" ]; then + exec lftp -u "$login,$password" "${options[@]}" "ftp://$host/$path" + else + exec lftp -u "${login}@${my_login}@${host},${password}@${my_password}" "${options[@]}" "ftp://$AUTHFTP_PROXY_HOST/$path" + fi +else + if [ -n "$noproxy" ]; then + exec ncftp -u "$login" -p "$password" "${options[@]}" "ftp://$host/$path" + else + exec ncftp -u "${login}@${my_login}@${host}" -p "${password}@${my_password}" "${options[@]}" "ftp://$AUTHFTP_PROXY_HOST/$path" + fi +fi diff --git a/bashrc b/bashrc new file mode 100644 index 0000000..c8dd759 --- /dev/null +++ b/bashrc @@ -0,0 +1,6 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function uprovide() { :; } +source @@dest@@/ulib/uenv || return +__uenv_source_dirs @@dest@@/bashrc.d "$HOME/etc/bashrc.d" +__uenv_cleanup diff --git a/caturl b/caturl new file mode 100755 index 0000000..1fa585e --- /dev/null +++ b/caturl @@ -0,0 +1,81 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Afficher une url + +USAGE + $scriptname " +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +function findurl() { + echo "$1" +} + +URL="$1" +URLFILE= + +if [ -z "$URL" ]; then + # Essayer de trouver une fichier homepage.{url,desktop} dans le répertoire + # courant + URLFILE="$(findurl homepage)" + [ -f "$URLFILE" ] || die "Il faut spécifier l'url à ouvrir" +elif [ -d "$URL" ]; then + # répertoire dans lequel se trouve éventuellement un fichier + # homepage.{url,desktop} + URLFILE="$(findurl "$URL/homepage")" + if [ -f "$URLFILE" ]; then + # un fichier d'url valide + : + else + # un répertoire local à ouvrir + URLFILE= + URL="file://$(abspath "$URL")" + fi +else + URLFILE="$(findurl "$URL")" + if [ -f "$URLFILE" ]; then + # un fichier d'url valide + : + else + if [ -f "$URL" ]; then + # un fichier local à ouvrir + URL="file://$(abspath "$URL")" + else + # on assume que c'est une url standard + : + fi + fi +fi + +if [ -n "$URLFILE" ]; then + # lire l'url dans un fichier + if [ "${URLFILE%.url}" != "$URL" ]; then + # raccourci vers une url de windows + URL="$(<"$URLFILE" awk 'BEGIN { type = 0; URL = "" } + tolower($0) == "[internetshortcut]" { type = type + 1 } + /[uU][rR][lL]=/ { URL = substr($0, 5) } +END { if (type == 1) print URL } +')" + [ -n "$URL" ] || die "$URLFILE: ne contient pas d'URL" + elif [ "${URLFILE%.desktop}" != "$URL" ]; then + # raccourci vers une url de XDG + URL="$(<"$URLFILE" awk 'BEGIN { type = 0; URL = "" } + tolower($0) == "[desktop entry]" { type = type + 1 } + /Type=Link/ { type = type + 1 } + /[uU][rR][lL]=/ { URL = substr($0, 5) } +END { if (type == 2) print URL } +')" + [ -n "$URL" ] || die "$URLFILE: ne contient pas d'URL" + fi +fi + +echo "$URL" diff --git a/compileAndGo b/compileAndGo new file mode 100755 index 0000000..ccf34d4 --- /dev/null +++ b/compileAndGo @@ -0,0 +1,625 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# See notice of copyright and license at end. + +# If you use this program for a #! script, please include the following +# as the second line of the script: +# See http://Yost.com/computers/compileAndGo + +# Bug: doesn't recompile if invoked file is a symlink. Fixed? +# Needs a call to realpath. + +## Bug: The following fails for the cg command after fixing it to work for compileAndGo +# Something about evaluation order of the variables in the source header. +# compiler = gcc +# compilerArgs = -O2 -o $cacheDir/$executableFilename $cacheDir/$sourceFilename + +if [ $# == 1 -a "$1" == --help ]; then + echo "compileAndGo: see http://Yost.com/computers/compileAndGo" + exit 0 +fi + +# The #! compileAndGo script that invoked us. +# If $0 is a symlink, this is the invoked name, not the symlink referent name +typeset -r commandFile="$1" + +pathOfDirPartOfExecPath() { + if [[ -h "$1" ]] ; then + local lsout="$(ls -l "$1")" + local referent="${lsout#* -> }" + pathOfDirPartOfExecPath "$referent" + elif [[ -d "${1%/*}" ]] ; then + echo "${1%/*}" + else + echo . + fi +} +typeset -r commandDir="$(pathOfDirPartOfExecPath "$0")/" +# If $0 is a symlink, this is the invoked name, not the symlink referent name + +ourName=${0##*/} + +#------------------------------------------------------------------------------- + +extensionToLanguage() { + case "$1" in + js) + echo javascript + ;; + java) + echo java + ;; + c) + echo c + ;; + cp | cpp | C | cxx | cc) + echo c++ + ;; + *) + ;; + esac +} + +languageToCompiler() { + case "$1" in + javascript) + echo jsc + ;; + java) + echo javac + ;; + c) + if [[ -x /usr/bin/gcc ]] ; then + echo /usr/bin/gcc + else + echo /usr/bin/cc + fi + ;; + c++) + echo /usr/bin/g++ + ;; + *) + ;; + esac +} + +echo2() { + # This works for sh, ksh, and bash, but not zsh. + echo "$@" +} + +echoVariables() { + echo 1>&2 $1 + if [[ ! $ourName == cg ]] ; then + # Echo all but the builtins. + echo 1>&2 "$( + eval "$( + echo "$cmdSetters" \ + | grep -v 'commandBasename +commandDir +sourceFilename +language +mainClass +executableFilename +compilerDir +classPath +compilerArgs +linkerArgs +execute +firstArgIsCommandName +verbose' \ + | sed " + s,^eval,echo, + s,=, = , + s,',,g + " + )" \ + | sed " + s,= ,= ', + s,$,', + " + )" + else + echo 1>&2 commandName = "$commandName" + echo 1>&2 compiler = "$compiler" + fi + # Echo the builtins. + echo 1>&2 commandBasename = "$commandBasename" + echo 1>&2 commandDir = "$commandDir" + echo 1>&2 sourceFilename = "$sourceFilename" + echo 1>&2 language = "$language" + echo 1>&2 mainClass = "$mainClass" + echo 1>&2 executableFilename = "$executableFilename" + echo 1>&2 compilerDir = "$compilerDir" + echo 1>&2 classPath = "$classPath" + echo 1>&2 compilerArgs = "$compilerArgs" + echo 1>&2 linkerArgs = "$linkerArgs" + echo 1>&2 execute = "$execute" + echo 1>&2 firstArgIsCommandName = "$firstArgIsCommandName" + echo 1>&2 verbose = "$verbose" +} + +#------------------------------------------------------------------------------- + +# If we use zsh, we could do this: +# zmodload zsh/stat +# stat -F "%Y-%m-%d_%H-%M-%S" +mtime . + +ls-linux() { + # 11742 2005-07-28 11:54:01.000000000 + ( + ls -dl --full-time --time-style=full-iso "$1" \ + | sed 's,.*[0-9] \([12][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\) \([0-9][0-9]\):\([0-9][0-9]\):\([0-9][0-9]\).[0-9]* .*,\1.\2-\3-\4,' + ) 2> /dev/null +} + +ls-oldlinux() { + # 11742 Tue Mar 01 17:22:50 2005 + ( + ls -dl --full-time "$1" \ + | sed 's,.*[0-9] ... \(...\) \([0-9][0-9]\) \([0-9][0-9]\):\([0-9][0-9]\):\([0-9][0-9]\) \([12][0-9][0-9][0-9]\) .*,\4-\1-\2.\3-\5-\6,' + ) 2> /dev/null +} + +ls-bsd() { + # 11742 Jul 28 12:38:31 2005 + ( + ls -dlT "$1" \ + | awk ' + BEGIN { + months["Jan"] = "01" ; months["Feb"] = "02" ; months["Mar"] = "03" + months["Apr"] = "04" ; months["May"] = "05" ; months["Jun"] = "06" + months["Jul"] = "07" ; months["Aug"] = "08" ; months["Sep"] = "09" + months["Oct"] = "10" ; months["Nov"] = "11" ; months["Dec"] = "12" + } + { + month = sprintf(months[$6]) ; day = $7 ; time = $8 ; year = $9 # What about Europe? + gsub(":", "-", time) + date = year "-" month "-" day "_" time + print date + } + ' + ) 2> /dev/null +} + +#------------------------------------------------------------------------------- + +set -e + +sourceInput= +case $ourName in +cg) + # Invoked as cg + if [[ $# == 0 ]] ; then + echo 1>&2 "Usage: cg sourcefile. [ args ... ]" + exit 2 + fi + sourceInput="$1" + export commandName=${commandFile##*/} + commandBasename="${commandName%.*}" + commandExtension="${commandFile##*.}" + sourceFilename=$commandName + language=$(extensionToLanguage $commandExtension) + compiler=$(languageToCompiler $language) + ;; +cgs) + sourceInput=/dev/stdin + export commandName=$ourName + commandBasename=$commandName + compilerOpt="$1" + case "$compilerOpt" in + -jsc) + sourceFileName=$commandName.js + ;; + -javac | -gcj | -jikes) + shift + export commandName=$1 + commandBasename=$commandName + sourceFileName=$commandName.java + ;; + -gcc | -c99 | -c89 | -cc) + sourceFileName=$commandName.c + ;; + -g++) + sourceFileName=$commandName.cp + ;; + "") + echo 1>&2 cgs: missing compiler option + exit 2 + ;; + *) + echo 1>&2 cgs: unknown compiler option: "$compilerOpt" + exit 2 + ;; + esac + compiler=${compilerOpt/-/} + ;; +*) + sourceInput= + # Invoked as compileAndGo + # Collect the variable declarations at the top of $commmandFile. + declarations="$( + sed -n ' + s,^[ ]*,, + /^!#$/q + /^compileAndGo/q + /^[ ]*#/d + s,^commandName,export commandName, + /^echo[ ]/{ + s/$/ ;/p + d + } + s,\$\({*commandBasename\),\\$\1,g + s,\$\({*commandDir\),\\$\1,g + s,\$\({*sourceFilename\),\\$\1,g + s,\$\({*language\),\\$\1,g + s,\$\({*mainClass\),\\$\1,g + s,\$\({*executableFilename\),\\$\1,g + s,\$\({*compilerDir\),\\$\1,g + s,\$\({*classPath\),\\$\1,g + s,\$\({*compilerArgs\),\\$\1,g + s,\$\({*linkerArgs\),\\$\1,g + s,\$\({*execute\),\\$\1,g + s,\$\({*cacheDir\),\\$\1,g + s,\$\({*firstArgIsCommandName\),\\$\1,g + s,[ ]*=[ ]*,=", + s,$," ;, + p + ' "$commandFile" + )" + eval $declarations + [[ ! -z ${commandName:="${1##*/}"} ]] + commandBasename="${commandName%.*}" + if (( 0$verbose >= 5 )) ; then + echo 1>&2 \=== Declarations + echo 1>&2 "$declarations" + fi + if [[ -z "$compiler" ]] ; then + if [[ -z "$language" ]] ; then + echo 1>&2 compileAndGo: compiler or language must be set + trouble=true + fi + compiler=$(languageToCompiler $language) + fi + if [[ ! -z "$trouble" ]] ; then + exit 2 + fi + ;; +esac + +#------------------------------------------------------------------------------- +# Collect the source code + +newsed() { + local arg + if sed --regex-extended < /dev/null >& /dev/null ; then + arg=--regex-extended + else + arg=-E + fi + sed $arg "$@" +} + +case "$sourceInput" in +/dev/stdin) + # from stdin + sourceCode=$(cat) + ;; +'') + # from the end of $commandFile + sourceCode=$( + newsed ' + 1,/^(!#|[ ]*compileAndGo)/s,.*,, + ' "$commandFile" + ) + if [[ -z "$sourceCode" ]] ; then + echo 1>&2 "$commandName: Missing '#!compileAndGo' line before source code starts." + exit 2 + fi + ;; +*) + # from the filename as first argument + sourceCode=$(cat $1) + ;; +esac + +#------------------------------------------------------------------------------- +# Construct the cacheDir variable. + +abi=$(uname -sm) +abi=${abi// /-} + +# Why must I use `` instead of $() here? +id=` + case "$sourceInput" in + /dev/stdin) + local tmp=($(echo "$sourceCode" | cksum)) + echo ${tmp[0]}${tmp[1]} + ;; + *) + case "$abi" in + Linux* | CYGWIN*) + ls-linux "$1" \ + || ls-oldlinux "$1" + ;; + *) + ls-bsd "$1" \ + || ls-linux "$1" \ + || ls-oldlinux "$1" + ;; + esac \ + || ( + local tmp=($(echo "$sourceCode" | cksum)) + echo ${tmp[0]}${tmp[1]} + ) + ;; + esac +` +compilerPath=$(type -p "$compiler" 2> /dev/null) || compilerPath=$compiler +realHOME=$(eval 'echo ~'$(whoami)) + +if [[ -x $realHOME/Library/Caches ]] ; then + # Mac OS X + cacheDirRoot=$realHOME/Library/Caches/CompileAndGo +else + cacheDirRoot=$realHOME/.compileAndGo +fi +cacheDirParent=$cacheDirRoot/${commandName} +cacheDir=$cacheDirParent/$abi/${id}_${compilerPath//\//-} + +#------------------------------------------------------------------------------- +# Apply defaults and then set the variables again. + +compilerName=${compiler##*/} + +# Some settings common among different compiler groups: +case $compilerName in +javac* | jikes*) + [[ ! -z ${mainClass:="$commandBasename"} ]] + [[ ! -z ${sourceFilename:="${mainClass}.java"} ]] + ;; +gcj*) + [[ ! -z ${mainClass:="$commandBasename"} ]] + [[ ! -z ${sourceFilename:="${mainClass}.java"} ]] + [[ ! -z ${executableFilename:="$commandBasename"} ]] + [[ ! -z ${execute:="PATH=$cacheDir:$PATH $executableFilename"} ]] + ;; +gcc* | g++* | c89* | c99*) + [[ ! -z ${executableFilename:="$commandBasename"} ]] + [[ ! -z ${execute:="PATH=$cacheDir:$PATH $executableFilename"} ]] + ;; +esac + +case $compilerName in +jsc*) + [[ ! -z ${mainClass:="$commandBasename"} ]] + [[ ! -z ${sourceFilename:="${mainClass}.js"} ]] + [[ ! -z ${executableFilename:="${mainClass}.class"} ]] + [[ ! -z "${execute:="${javaBinDir}java -cp $cacheDir${classPath:+:$classPath} $mainClass"}" ]] + [[ ! -z "${compilerArgs:="-o $executableFilename $cacheDir/$sourceFilename"}" ]] + compileCmd="java -cp $cacheDir${classPath:+:$classPath} org.mozilla.javascript.tools.jsc.Main $compilerArgs" + ;; +javac* | jikes*) + [[ ! -z ${executableFilename:="${mainClass}.class"} ]] + sourceVersion= + case $compilerName in + javac*) + if [[ $compilerName == $compiler ]] ; then + compilerDir= + else + compilerDir=${compiler%/*}/ + fi + [[ ! -z "${execute:="${compilerDir}java -cp $cacheDir${classPath:+:$classPath} $mainClass"}" ]] + # Prepare to tell javac to compile for the latest language version it supports + sourceVersion="-source $(${compilerDir}java -version 2>&1 | sed -n '1s,[^"]*"\([1-9][1-9]*\.[1-9][1-9]*\).*,\1,p')" + ;; + jikes*) + if [[ -z "$classPath" && -z "$compilerArgs" && -z "$CLASSPATH" ]] ; then + # Mac: export CLASSPATH=/System/Library/Frameworks/JavaVM.framework/Classes/classes.jar + echo 1>&2 compileAndGo: for jikes, if neither classPath nor CLASSPATH are set, compilerArgs must be set. + exit 2 + fi + [[ ! -z "${execute:="java -cp $cacheDir${classPath:+:$classPath} $mainClass"}" ]] + ;; + esac + [[ ! -z "${compilerArgs:="$sourceVersion -d $cacheDir -sourcepath . ${classPath:+-classpath $classPath} $cacheDir/$sourceFilename"}" ]] + compileCmd="$compiler $compilerArgs" + ;; +gcj*) + [[ ! -z ${compilerArgs:="--main=$mainClass -o $cacheDir/$commandBasename $cacheDir/$sourceFilename"} ]] + compileCmd="$compiler $compilerArgs $linkerArgs" + ;; +gcc* | g++* | c89* | c99* | clang | clang++) + case $compilerName in + cc* | gcc* | c89* | c99* | clang) + [[ ! -z ${sourceFilename:="${commandName}.c"} ]] + ;; + g++* | clang++) + [[ ! -z ${sourceFilename:="${commandName}.cp"} ]] + ;; + esac + [[ ! -z ${compilerArgs:="-O2 -o $cacheDir/$executableFilename $cacheDir/$sourceFilename"} ]] + compileCmd="$compiler $compilerArgs $linkerArgs" + ;; +esac + +#------------------------------------------------------------------------------- +# Set the variables + +if [[ ! $ourName == cg ]] ; then + vars=$( + echo "$declarations" \ + | sed -n 's,\([^=]*\)=.*,\1,p' + ) + cmdSetters=$( + for x in $vars + do + echo eval "'"${x}='"'$(eval echo \$$x)'"'"'" + done + ) + eval "$cmdSetters" +fi +if (( 0$verbose >= 3 )) ; then + echoVariables "=== the variables before defaults" +fi +if [[ $ourName == cg ]] ; then + if (( 0$verbose >= 4 )) ; then + echo 1>&2 \=== eval command to set variables + echo2 1>&2 eval "$cmdSetters" + fi +fi + +#------------------------------------------------------------------------------- +# Check that all the required variables are set. +for x in sourceFilename executableFilename compilerArgs execute +do + eval 'if [ -z "'\$$x'" ] ; then trouble=true ; fi' +done +if [[ ! -z "$trouble" ]] ; then echo 1>&2 compileAndGo: unknown compiler setting "$compiler" ; exit 2 ; fi +for x in sourceFilename executableFilename compilerArgs execute +do + eval 'if [ -z "'\$$x'" ] ; then echo 1>&2 compileAndGo: $x must be set ; fi' +done +if [[ ! -z "$trouble" ]] ; then exit 2 ; fi + +[[ ! -z ${firstArgIsCommandName:=false} ]] +[[ ! -z ${verbose:=0} ]] + +eval "$cmdSetters" + +if (( 0$verbose >= 3 )) ; then + echoVariables "=== the variables after defaults" +fi + +#set -x + +#------------------------------------------------------------------------------- +# Compile if necessary + +# shorthand +cachedExecutable=$cacheDir/$executableFilename + +# The security precautions go like this: +# The executable and the folder in which it resides are +# * owned by user +# * 700 permissions (rwx------) +# The important facts are: +# * Only the user or root can chmod a file or folder owned by him. +# * Only the user or root can write into a file or folder that is 700. +# * Only root can chown a file or folder to the user. +# so only the user or root can construct a suitable file in the suitable +# folder. No one else can. That's about as good as it can get on unix. +# The attack would be limited to finding some existing folder containing +# an executable of the correct name, both owned by the user and 700, +# then moving the folder into the appropriate path. +# The implementation should be expanded to require that all folders from +# $cacheDir through $cacheDirParent must be owned by user and be 700. +if [[ ! -O $cachedExecutable ]] ; then + if [[ -e $cachedExecutable ]] ; then + echo 1>&2 "$commandName: Aborting because $cachedExecutable exists," + echo 1>&2 "$commandName: and you don't own it." + echo 1>&2 "$commandName: This is a possible security violation." + exit 2 + fi + + # Try to make it harder for others to tamper with our cache. + umask 077 + # Insist that $cacheDirParent is a directory and is owned by the user. + if [[ -d $cacheDirParent ]] ; then + if [[ ! -O $cacheDirParent ]] ; then + echo 1>&2 "$commandName: Aborting because $cacheDirParent/ exists, and you don't own it." + echo 1>&2 "$commandName: This is a security risk." + exit 2 + fi + chmod 700 $cacheDirParent + else + mkdir -p $cacheDirParent + echo > $cacheDirParent/../README "See http://Yost.com/computers/compileAndGo" + fi + + mkdir -p $cacheDir + + # Compile the source. + if (( 0$verbose == 1 )) ; then + echo 1>&2 "[ $commandName: compiling. ]" + elif (( 0$verbose >= 2 )) ; then + echo -n 1>&2 "[ " + echo2 -n 1>&2 "$compileCmd" + echo 1>&2 " ]" + fi + echo "$sourceCode" > $cacheDir/$sourceFilename + eval $compileCmd + + # Make a canonical name we can look at to determine access time. + ln -f $cachedExecutable $cacheDir/.executable +fi + +#------------------------------------------------------------------------------- +# Execute the built program. + +if [[ "$firstArgIsCommandName" != true ]] ; then + shift +fi + +if (( 0$verbose >= 2 )) ; then + echo -n 1>&2 "[ " + echo2 -n 1>&2 $execute "$@" + echo 1>&2 " ]" +fi +eval "$execute"' "$@"' +status=$? + +if [[ true ]] ; then + # Run a background task to clean the cache occasionally. + ( + # Check every this-many days. + checkInterval="-mtime -7" + # Max number of days a version cam be unused and not be removed. + maxAge="-atime +14" + # Every $checkInterval days, remove stuff not used in $maxAge days. + stamp=$(nice -n 20 find $cacheDirRoot -maxdepth 1 -name .timestamp $checkInterval) + if [[ ! -z "$stamp" ]] ; then + # Too soon + exit + fi + nice -n 20 touch $cacheDirRoot/.timestamp + # Remove dirs of executable versions not accessed in the last $maxAge days. + candidates=$(nice -n 20 find $cacheDirRoot -mindepth 3 -name .executable $maxAge) + if [[ ! -z "$candidates" ]] ; then + #echo "$candidates" + echo "$candidates" \ + | nice -n 20 sed 's,/.executable,,' \ + | nice -n 20 xargs rm -rf + fi + ) \ + > /dev/null 2>&1 \ + & +fi + +exit $status + +# Copyright 2005-2010 Dave Yost +# All rights reserved. +# This version is +# compileAndGo 5.0 2010-11-06 +# which at time of this publication can be found at: +# http://Yost.com/computers/compileAndGo +# Redistribution and use in the form of source code or derivative data built +# from the source code, with or without modification, are permitted provided +# that the following conditions are met: +# 1. THE USER AGREES THAT THERE IS NO WARRANTY. +# 2. If and only if appropriate, the above phrase "This version is" must be +# followed by the phrase "a modified form of" or "extracted from" or +# "extracted and modified from". +# 3. Redistributions of source code must retain this notice intact. +# 4. Redistributions in the form of derivative data built from the source +# code must reproduce this notice intact in the documentation and/or other +# materials provided with the distribution, and each file in the derivative +# data must reproduce any Yost.com URI included in the original distribution. +# 5. Neither the name of Dave Yost nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# 6. Written permission by the author is required for redistribution as part +# of a commercial product. +# This notice comprises all text from "Copyright" above through the end of +# this sentence. diff --git a/cssh b/cssh new file mode 100755 index 0000000..57a730a --- /dev/null +++ b/cssh @@ -0,0 +1,80 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Faire une connexion ssh en lançant automatiquement un screen sur l'hôte distant + +USAGE + $scriptname [user@]host [options] + +OPTIONS +note: les options de ssh doivent se trouver *APRES* [user@]host, pour simplifier +leur analyse. +L'option -t de ssh est forcée, pour permettre la connexion par screen +Les options suivantes doivent se trouver *AVANT* le premier argument: + -S SSH + Spécifier l'exécutable à utiliser pour lancer ssh." +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +ssh= +parse_opts + "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -S:,--ssh ssh= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +userhost="$1"; shift +[ -n "$userhost" ] || die "Vous devez spécifier l'hôte sur lequel faire la connexion" + +[ -n "$ssh" ] || ssh=ssh + +exec "$ssh" -t "$@" "$userhost" /bin/bash -c \'' +function __ask() { + local r + read -p "$1" -t 2 r + [ $? -gt 128 -o -z "$r" -o "$r" == "o" -o "$r" == "O" -o "$r" == "y" -o "$r" == "Y" ] +} + +function __auto_screen() { + # Si on est déjà dans screen, ne rien faire + [ -z "$STY" ] || return + + local msgprefix + local screens count + + screens="$(LANG=C screen -ls | grep -Ei "attached|detached")" + if [ -n "$screens" ]; then + count="$(echo "$screens" | wc -l)" + else + count=0 + fi + if [ $count -gt 0 ]; then + if [ $count -eq 1 ]; then + msgprefix="Il y a '"$COULEUR_BLEUE"'1 session screen en cours'"$COULEUR_NORMALE"' +Cette session" + else + msgprefix="Il y a '"$COULEUR_ROUGE"'$count sessions screen en cours'"$COULEUR_NORMALE"': +$screens +La première session" + fi + if __ask " +$msgprefix sera reconnectée automatiquent dans 2 secondes +Voulez-vous reconnecter la session screen? [On] "; then + exec screen -q -s -/bin/bash -xRR + else + exec /bin/bash -l + fi + elif __ask " +Une '"$COULEUR_VERTE"'nouvelle session screen'"$COULEUR_NORMALE"' sera lancée automatiquement dans 2 secondes +Voulez-vous lancer une session screen? [On] "; then + exec screen -q -s -/bin/bash -RR + else + exec /bin/bash -l + fi +} + +__auto_screen +'\' diff --git a/doc/.udir b/doc/.udir new file mode 100644 index 0000000..b253f08 --- /dev/null +++ b/doc/.udir @@ -0,0 +1,6 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Utiliser 'udir --help-vars' pour une description de la signification des +# variables suivantes: +udir_desc="" +udir_note="" +udir_types=(wikidir) diff --git a/doc/DefaultTiddlers.twp b/doc/DefaultTiddlers.twp new file mode 100644 index 0000000..976ae39 --- /dev/null +++ b/doc/DefaultTiddlers.twp @@ -0,0 +1,25 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Pense-bête: +# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ +# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ +# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] +# [[external|http://site.com]] ---- {{monospace}} +# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 +# * dotlist ** sublist # numlist ## sublist +# {{{ |caption|c [img[title|filename]] +# pre text |!header|!header|h [img[filename]] +# }}} |cell|cell| [img[title|filename][link]] +# <<< |>|colspan| [img[filename][link]] +# blockquote |rowspan|one| [img[filename]] +# >quote1 |left| right| +# >>quote2 |>| center | +# >>>quote3 +##@creator: jclain +##@created: 09/03/2012 05:08 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: DefaultTiddlers + +[[Main]] diff --git a/doc/EnsureVM.twp b/doc/EnsureVM.twp new file mode 100644 index 0000000..85493f8 --- /dev/null +++ b/doc/EnsureVM.twp @@ -0,0 +1,16 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: EnsureVM + +{{{ +EnsureVM: s'assurer que les services sont lancés pour un type de virtualisation + +USAGE + EnsureVM type + +Les types supportés sont virtualbox et kvm (par défaut) +}}} diff --git a/doc/Main.twp b/doc/Main.twp new file mode 100644 index 0000000..4628491 --- /dev/null +++ b/doc/Main.twp @@ -0,0 +1,46 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Pense-bête: +# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ +# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ +# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] +# [[external|http://site.com]] ---- {{monospace}} +# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 +# * dotlist ** sublist # numlist ## sublist +# {{{ |caption|c [img[title|filename]] +# pre text |!header|!header|h [img[filename]] +# }}} |cell|cell| [img[title|filename][link]] +# <<< |>|colspan| [img[filename][link]] +# blockquote |rowspan|one| [img[filename]] +# >quote1 |left| right| +# >>quote2 |>| center | +# >>>quote3 +##@creator: jclain +##@created: 09/03/2012 05:08 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: Main + +!Présentation +nutools est un ensemble d'utilitaires pour faciliter l'utililisation des Unixes, en particulier Linux, mais aussi MacOS X et Cygwin. +C'est aussi une librairie de scripts shell réutilisables ([[ulib]]) et une librairie de modules python réutilisables (pyulib) + +!Prérequis +Python >= 2.3 et GNU Awk sont requis pour que toutes les fonctionnalités soient supportées. +* Sous Linux, lors de l'installation du package, les meilleurs efforts sont fait pour que ces packages soient installés. +* Sous MacOSX, il faut installer manuellement Fink, DarwinPorts ou Homebrew + +! Outils +Chaque outil contient une aide intégrée. Il suffit de lancer l'outil avec l'argument {{{--help}}} pour avoir une aide détaillée. +* Déploiement d'un répertoire ou d'une archive +** [[uinst]]: Déploiement local +** [[mkusfx]]: Faire une archive auto-installable avec uinst +** [[ruinst]]: Déploiement distant avec uinst +** [[runs]]: Lancer un script avec le protocole RUNS +** [[rruns]]: Déploiement distant avec runs +* Librairie réutilisable de scripts shell +** [[uinc]]: Dépliage des inclusions dans un fichier +** [[ulibsync]]: Faire une copie locale pour un projet de ulib et/ou pyulib +* Autres outils +** [[udir]]: Gestion des paramètres d'un répertoire. Ces paramètres sont entre autres utilisés par uinst et uinc. diff --git a/doc/MainMenu.twp b/doc/MainMenu.twp new file mode 100644 index 0000000..79a4dfa --- /dev/null +++ b/doc/MainMenu.twp @@ -0,0 +1,25 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Pense-bête: +# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ +# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ +# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] +# [[external|http://site.com]] ---- {{monospace}} +# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 +# * dotlist ** sublist # numlist ## sublist +# {{{ |caption|c [img[title|filename]] +# pre text |!header|!header|h [img[filename]] +# }}} |cell|cell| [img[title|filename][link]] +# <<< |>|colspan| [img[filename][link]] +# blockquote |rowspan|one| [img[filename]] +# >quote1 |left| right| +# >>quote2 |>| center | +# >>>quote3 +##@creator: jclain +##@created: 09/03/2012 05:08 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: MainMenu + +[[GettingStarted]] diff --git a/doc/RUNS.txt b/doc/RUNS.txt new file mode 100644 index 0000000..94b0217 --- /dev/null +++ b/doc/RUNS.txt @@ -0,0 +1,152 @@ +# -*- coding: utf-8 mode: text -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +Les scripts RUNS sont des scripts servant à déployer certains services ou à +faire certaines opérations. + +runs diffère un peu de uinst, qui est orienté copie de fichiers et configuration +d'une arborescence. Avec runs, chaque script fait une seule opération, en +utilisant éventuellement certains fichiers disponibles dans le répertoire du +script. + +RUNSPATH contient une liste de répertoires qu'il faut initialiser avec la +commande 'runs --init'. L'arborescence est de la forme: + + runsdir + +- scripts + | \_ SCRIPTS... + +- hosts + | \_ HOSTS... + | + runs.conf + | + sysinfos.conf + | + default + | \_ SCRIPTS... + \_ DOMAINS... + \_ HOSTS... + + runs.conf + + sysinfos.conf + + default + \_ SCRIPTS... + +Le répertoire scripts contient les scripts généraux. + +SCRIPTS correspondant aux fichiers de scripts, qui n'ont pas de contrainte sur +la façon de les nommer. HOSTS correspond aux répertoires d'hôtes, qui peuvent +contenir des fichiers de configuration ou d'autres scripts. DOMAINS correspond +aux domaines de hôtes pleinement qualifiés. Ces répertoire contiennent des +répertoires d'hôtes. + +Le répertoire hosts contient des configuration pour des hôtes sans domaine. Les +hôtes pleinements qualifiés sont classés par domaine, puis par nom d'hôte. Par +exemple, si on cherche un script ou un fichier de configuration pour l'hôte +'medusa.univ.run', il sera d'abord cherché dans 'runsdir/univ.run/medusa' puis +dans 'runsdir/hosts/medusa' + +Le fichier runs.conf contient des configuration qui sont partagées par tous les +scripts qui sont dans l'arborescence en-dessous. Ce fichier est au format CONF +(cf. ci-dessous). + +Le fichier sysinfos.conf contient si nécessaire la configuration du système pour +l'hôte, pour les scripts qui doivent faire des préparations en local en fonction +du système distant. Ce fichier peut-être initialisé avec la configuration +affichée par le script usysinfos + +Le fichier default contient la recette par défaut pour la configuration de +l'hôte. Il s'agit d'une suite de lignes de la forme: + + script [params] + [params] + ... + +params correspond aux arguments de la fonction var. Chaque ligne indentée sous +le nom du script correspond à une invocation différente de var. Par exemple, +avec cette commande: + + script name=value arr0+=value0 arr1-=value1 + arr2 value21 value22 + +Le script 'script' est lancé avec la configuration de variable suivante: +name=value, rajouter value0 au tableau arr0, enlever value1 au tableau arr1, et +initialiser le tableau arr2 avec les valeur value21 et value22 + +Un script est composé de deux sections: CONF et SCRIPT, séparés par la ligne +'script:'. La forme d'un script est donc: + + CONF + script: + SCRIPT + +Les deux sections sont écrites dans le langage de script bash. + +Format de la section CONF +========================= + +Dans cette section, les fonctions notamment disponibles sont: desc, conf, var, +out, ref, sysinfos + +desc DESC + Donner la description du script, à afficher avec 'runs --info' + +conf flags... + Activer certains flags. Les valeurs possible sont: + root -- ce script requière d'être lancé avec l'utilisateur root + local -- dans le cas d'un déploiement distant, ce script doit être lancé + uniquement en local + +after scriptpaths... + Requérir que dans une même session, ce script soit lancé après tous les + scripts mentionnés + +after -r scriptpath [args...] + Si le script spécifié a déjà été lancé dans la session en cours, ne rien + faire. Sinon, lancer le script spécifié avec les arguments spécifiés avant + de lancer le script en cours. + +after -rr scriptpath [args...] + Lancer de façon inconditionnelle le script spécifié avec les arguments + spécifiés avant de lancer le script en cours. + +var name=value + Initialiser la variable name à la valeur value + +var array+=value + Ajouter la valeur value au tableau array + +var array-=value + Enlever la valeur value du tableau array + +var array value0 value1... valueN + Initialiser le tableau array avec la valeur (value0 value1...valueN) + +out name[=file] + Créer un fichier temporaire nommé file dans un espace partagé, et mettre son + chemin absolu dans la variable name. + +ref [-r] name[=file] + Mettre dans la variable name le chemin absolu vers le fichier file, cherché + d'abord dans le répertoire du script, puis dans le répertoire partagé, et + enfin dans RUNSPATH. Si le fichier n'est trouvé nulle part, le chemin est + relatif au répertoire partagé. + Avec l'option -r, le fichier est requis: le script s'arrête si le fichier + référencé n'est pas trouvé. + +Format de la section SCRIPT +=========================== + +Cette section peut contenir n'importe quelle script. Par défaut, les librairies +DEFAULTS et runs sont chargées. Mais il est possible avec urequire de charger +d'autres librairies, notamment uinst ou uinc. + +Les fonctions suivantes sont disponibles: shouldrun, setdone, resetdone. + +shouldrun [subsystem [value]] + Tester si le script (ou l'opération subsystem du script) doit être + lancée. Tant que la fonction 'setdone' n'est pas lancée, cette fonction + retourne vrai. + +setdone [subsystem [value]] + Spécifier que le script (ou l'opération subsystem du script) a été lancée + correctement. + +resetdone [subsystem] + Réinitialiser les informations concernant l'état d'installation du script ou + de l'opération subsystem du script. diff --git a/doc/SKvm.twp b/doc/SKvm.twp new file mode 100644 index 0000000..ab0de5d --- /dev/null +++ b/doc/SKvm.twp @@ -0,0 +1,30 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: SKvm + +{{{ +SKvm: lancer une machine virtuelle kvm + +USAGE + SKvm [options] vmName + SKvm {-l|-A|-g} + +OPTIONS + -n Ne rien faire excepté s'assurer que les modules kvm sont chargés + -u Lancer l'opération avec les droits de l'utilisateur courant. Par défaut, + ce script tente d'acquérir les droits de root. + -l Lister les machines virtuelles + -s Démarrer la machine virtuelle (par défaut) + Si le nom de la machine virtuelle n'est pas spécifiée, un menu est + affiché + -k Arrêter la machine virtuelle + -H Arrêter sauvagement la machine virtuelle + -r Redémarrer la machine virtuelle + -S Enregistrer l'état de la machine virtuelle + -A Arrêter toutes les machines virtuelles qui tournent actuellement + -g Afficher le gestionnaire de machines virtuelle +}}} diff --git a/doc/SVirtualBox.twp b/doc/SVirtualBox.twp new file mode 100644 index 0000000..07db3f8 --- /dev/null +++ b/doc/SVirtualBox.twp @@ -0,0 +1,27 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: SVirtualBox + +{{{ +SVirtualBox: lancer une machine virtuelle VirtualBox + +USAGE + SVirtualBox [options] vmName + +OPTIONS + -n Ne rien faire excepté s'assurer que les modules VirtualBox sont chargés + -l Lister les machines virtuelles + -s Démarrer la machine virtuelle (par défaut) + Si le nom de la machine virtuelle n'est pas spécifiée, un menu est + affiché + -k Arrêter la machine virtuelle (par ACPI) + -p Mettre en veille la machine virtuelle (par ACPI) + -H Arrêter sauvagement la machine virtuelle + -R Redémarrer sauvagement la machine virtuelle + -S Enregistrer l'état de la machine virtuelle + -g Afficher le gestionnaire de machines virtuelle +}}} diff --git a/doc/SiteSubtitle.twp b/doc/SiteSubtitle.twp new file mode 100644 index 0000000..5b102c4 --- /dev/null +++ b/doc/SiteSubtitle.twp @@ -0,0 +1,25 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Pense-bête: +# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ +# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ +# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] +# [[external|http://site.com]] ---- {{monospace}} +# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 +# * dotlist ** sublist # numlist ## sublist +# {{{ |caption|c [img[title|filename]] +# pre text |!header|!header|h [img[filename]] +# }}} |cell|cell| [img[title|filename][link]] +# <<< |>|colspan| [img[filename][link]] +# blockquote |rowspan|one| [img[filename]] +# >quote1 |left| right| +# >>quote2 |>| center | +# >>>quote3 +##@creator: jclain +##@created: 09/03/2012 05:08 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: SiteSubtitle + +Outils divers pour linux/macosx, et librairies pour bash diff --git a/doc/SiteTitle.twp b/doc/SiteTitle.twp new file mode 100644 index 0000000..f1c9067 --- /dev/null +++ b/doc/SiteTitle.twp @@ -0,0 +1,25 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Pense-bête: +# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ +# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ +# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] +# [[external|http://site.com]] ---- {{monospace}} +# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 +# * dotlist ** sublist # numlist ## sublist +# {{{ |caption|c [img[title|filename]] +# pre text |!header|!header|h [img[filename]] +# }}} |cell|cell| [img[title|filename][link]] +# <<< |>|colspan| [img[filename][link]] +# blockquote |rowspan|one| [img[filename]] +# >quote1 |left| right| +# >>quote2 |>| center | +# >>>quote3 +##@creator: jclain +##@created: 09/03/2012 05:08 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: SiteTitle + +nutools diff --git a/doc/SiteUrl.twp b/doc/SiteUrl.twp new file mode 100644 index 0000000..dfdad8b --- /dev/null +++ b/doc/SiteUrl.twp @@ -0,0 +1,25 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Pense-bête: +# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ +# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ +# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] +# [[external|http://site.com]] ---- {{monospace}} +# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 +# * dotlist ** sublist # numlist ## sublist +# {{{ |caption|c [img[title|filename]] +# pre text |!header|!header|h [img[filename]] +# }}} |cell|cell| [img[title|filename][link]] +# <<< |>|colspan| [img[filename][link]] +# blockquote |rowspan|one| [img[filename]] +# >quote1 |left| right| +# >>quote2 |>| center | +# >>>quote3 +##@creator: jclain +##@created: 09/03/2012 05:08 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: SiteUrl + + diff --git a/doc/_root.twp b/doc/_root.twp new file mode 100644 index 0000000..46689b7 --- /dev/null +++ b/doc/_root.twp @@ -0,0 +1,12 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: _root + +{{{ +_root: devenir l'utilisateur root, avec 'sudo' si possible, ou 'su' si +'sudo' n'est pas installé +}}} diff --git a/doc/authftp.twp b/doc/authftp.twp new file mode 100644 index 0000000..b752caa --- /dev/null +++ b/doc/authftp.twp @@ -0,0 +1,18 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: authftp + +{{{ +authftp: Se connecter avec ncftp sur un site FTP authentifié +Ce script est conçu pour les sites qui utilisent un proxy FTP pour les connexion +authentifiées. + +USAGE + authftp [options] host login password [path] + +OPTIONS +}}} diff --git a/doc/caturl.twp b/doc/caturl.twp new file mode 100644 index 0000000..02f23d0 --- /dev/null +++ b/doc/caturl.twp @@ -0,0 +1,14 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: caturl + +{{{ +caturl: Afficher une url + +USAGE + caturl +}}} diff --git a/doc/compileAndGo.twp b/doc/compileAndGo.twp new file mode 100644 index 0000000..152d884 --- /dev/null +++ b/doc/compileAndGo.twp @@ -0,0 +1,11 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: compileAndGo + +{{{ +compileAndGo: see http://Yost.com/computers/compileAndGo +}}} diff --git a/doc/fconv.twp b/doc/fconv.twp new file mode 100644 index 0000000..dfd1174 --- /dev/null +++ b/doc/fconv.twp @@ -0,0 +1,27 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: fconv + +{{{ +fconv: convertir des fichiers dans un autre encoding + +USAGE + fconv [-f src_enc] [ -t dest_enc] [/path/to/file] + +OPTIONS + -f from + Encoding source. Si n'est pas spécifié ou vaut 'detect', l'encoding est + autodétecté. + -t to + Encoding destination. Doit être spécifié. + Cas particulier: si to vaut 'lf' ou 'crlf', from est ignoré, et seuls + les caractères de fin de lignes sont convertis. + -N Ne pas optimiser le calcul de l'encoding. Cette option n'est valide que + si -f n'est pas spécifié. On assume que tous les noms de fichiers n'ont + pas le même encoding. L'encoding from est donc recalculé à chaque fois. + -r inverser from et to, qui doivent être tous les deux spécifiés. +}}} diff --git a/doc/fnconv.twp b/doc/fnconv.twp new file mode 100644 index 0000000..9f573c4 --- /dev/null +++ b/doc/fnconv.twp @@ -0,0 +1,25 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: fnconv + +{{{ +fnconv: renommer des fichiers en changeant leur encoding + +USAGE + fnconv [-f src_enc] [ -t dest_enc] [/path/to/file] + +OPTIONS + -f from + Encoding source. Si n'est pas spécifié ou vaut 'detect', l'encoding est + autodétecté. + -t to + Encoding destination. Doit être spécifié. + -N Ne pas optimiser le calcul de l'encoding. Cette option n'est valide que + si -f n'est pas spécifié. On assume que tous les noms de fichiers n'ont + pas le même encoding. L'encoding from est donc recalculé à chaque fois. + -r inverser from et to, qui doivent être tous les deux spécifiés. +}}} diff --git a/doc/geturl.twp b/doc/geturl.twp new file mode 100644 index 0000000..45a2b65 --- /dev/null +++ b/doc/geturl.twp @@ -0,0 +1,14 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: geturl + +{{{ +geturl: Télécharger un fichier avec wget ou curl + +USAGE + geturl [wget options] +}}} diff --git a/doc/mkRewriteRules.twp b/doc/mkRewriteRules.twp new file mode 100644 index 0000000..b6b32bf --- /dev/null +++ b/doc/mkRewriteRules.twp @@ -0,0 +1,86 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: mkRewriteRules + +{{{ +mkRewriteRules: Créer un fichier de redirections pour Apache à partir d'un certain +nombre de règles + +USAGE + mkRewriteRules -f rewrite.rules [-o RewriteRules.conf] [-w RewriteRules.html] host + +OPTIONS + -p Générer les directives et tenir compte de proxy_acls + Par défaut, le champ proxy_acls est ignoré + +FORMAT des règles de mapping +============================ + +Les commentaires commencent par le signe "#" +Les règles sont de la forme: + src:dest:host:suffix:OPTS:prot:proxy_acls + ^prefix + =literal + +prot vaut par défaut http. Il peut valoir aussi https + +Si dest ou suffix se terminent par $, on est en mode NO_SLASH +En mode NO_SLASH, si src se termine par $, on est en mode NO_TRAIL + +* Si dest est de la forme Application.woa +En mode NO_SLASH, on génère + RewriteRule ^/src(.*) [prot://host]/cgi-bin/WebObjects/dest[/suffix]$1 [L,OPTS] +En mode NO_SLASH+NO_TRAIL, on génère + RewriteRule ^/src [prot://host]/cgi-bin/WebObjects/dest[/suffix] [L,OPTS] +En mode normal, on génère + RewriteRule ^/src$ /src/ + RewriteRule ^/src/(.*) [prot://host]/cgi-bin/WebObjects/dest[/suffix]/$1 [L,OPTS] + +* Si dest n'est pas de la forme Application.woa +En mode NO_SLASH, on génère + RewriteRule ^/src(.*) [prot://host]/dest[/suffix]$1 [L,OPTS] +En mode NO_SLASH+NO_TRAIL, on génère + RewriteRule ^/src [prot://host]/dest[/suffix] [L,OPTS] +En mode normal, on génère + RewriteRule ^/src$ /src/ + RewriteRule ^/src/(.*) /dest[/suffix]/$1 [L,OPTS] + +Si une règle est précédée d'une ou plusieurs lignes de la forme "^prefix", +ces lignes sont copiées avant chacune des commandes RewriteRule générées +pour une règle. Ceci permet d'ajouter des conditions avec RewriteCond pour +une règle. e.g. + ^RewriteCond %{REMOTE_ADDR} 10\..* + src:dest.woa +qui génère: + RewriteCond %{REMOTE_ADDR} 10\..* + RewriteRule ^/src$ /src/ + RewriteCond %{REMOTE_ADDR} 10\..* + RewriteRule ^/src/(.*) /cgi-bin/WebObjects/dest.woa/$1 [L] + +Une ligne de la forme "=literal" est recopiée sans modifications (sans le "=") +dans le fichier de sortie. + +proxy_acls est utilisé si l'option -p est spécifiée et OPTS contient P (comme +proxy), ou si le mode de réécriture requière l'utilisation d'un proxy. + +* Avec la valeur "None", aucune directive n'est générée +* Si aucune valeur n'est spécifiée, la directive suivante est générée: + + AddDefaultCharset off + Order Deny,Allow + Allow from all + +* Si une valeur est spécifiée, la directive suivante est générée: + + AddDefaultCharset off + Order Allow,Deny + Allow from $proxy_acls + + +Dans les exemples donnés ci-dessus, $URL est l'url générée par la réécriture, +et $proxy_acls la valeur du champ proxy_acls spécifiée ci-dessus. +}}} diff --git a/doc/mkiso.twp b/doc/mkiso.twp new file mode 100644 index 0000000..38ac6ae --- /dev/null +++ b/doc/mkiso.twp @@ -0,0 +1,18 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:19 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: mkiso + +{{{ +mkiso: créer une image iso d'un répertoire + +USAGE + mkiso [options] srcdir [dest.iso] + +OPTIONS + -M, --hfs + créer une image hybride ISO/HFS +}}} diff --git a/doc/mkurl.twp b/doc/mkurl.twp new file mode 100644 index 0000000..ea356bd --- /dev/null +++ b/doc/mkurl.twp @@ -0,0 +1,20 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: mkurl + +{{{ +mkurl: Enregistrer une url dans un fichier raccourci + +USAGE + mkurl [output] + +OPTIONS +Par défaut, l'url est enregistrée dans un fichier homepage.url +Mais il est possible de spécifier un fichier avec l'extension .url pour un +raccourci utilisable aussi sous Windows, ou avec l'extension .desktop pour +compatibilité avec le standard XDG +}}} diff --git a/doc/mkusfx.twp b/doc/mkusfx.twp new file mode 100644 index 0000000..4972d56 --- /dev/null +++ b/doc/mkusfx.twp @@ -0,0 +1,35 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: mkusfx + +{{{ +mkusfx: Créer une archive auto-extractible qui installe son contenu avec uinst + +USAGE + mkusfx [options] [--bare] src cmd... + mkusfx [options] [--uinst] src + +OPTIONS + --bare + Installer le contenu de l'archive en lançant la commande 'cmd...' avec + le répertoire courant étant le contenu de l'archive. Typiquement, ce + sera une commande de forme './script', où script est un fichier situé à + la racine de l'archive + Dans ce mode d'installation, l'option --self-contained est ignorée. + --uinst + Installer le contenu de l'archive avec uinst (par défaut) + -o dest + Spécifier le fichier de sortie. Par défaut, il s'agit de + ${src}-installer.run + --tmp-archive + Spécifier qu'il s'agit d'une archive temporaire. Cette archive + s'auto-détruit après utilisation. + --self-contained + Spécifier que l'archive doit pouvoir s'installer même sur un système sur + lequel nutools n'est pas installé. Cette archive contiendra une copie + locale de ulib et uinst.sh +}}} diff --git a/doc/openurl.twp b/doc/openurl.twp new file mode 100644 index 0000000..72eeb5a --- /dev/null +++ b/doc/openurl.twp @@ -0,0 +1,14 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: openurl + +{{{ +openurl: Ouvrir une URL dans un navigateur + +USAGE + openurl +}}} diff --git a/doc/rmtildes.twp b/doc/rmtildes.twp new file mode 100644 index 0000000..448e3e3 --- /dev/null +++ b/doc/rmtildes.twp @@ -0,0 +1,16 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: rmtildes + +{{{ +rmtildes: supprimer les fichiers *~ dans le répertoire courant + +USAGE + rmtildes [dir [glob]] + +Par défaut, dir==. et glob==*~ +}}} diff --git a/doc/rruns.twp b/doc/rruns.twp new file mode 100644 index 0000000..3c1f328 --- /dev/null +++ b/doc/rruns.twp @@ -0,0 +1,62 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 30/03/2012 04:42 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: rruns + +{{{ +rruns: Déploiement distant avec runs + +USAGE + rruns [-H host] [-T tmproot] rscriptname name=value... + rruns [-H host] [-T tmproot] @recipe name=value... + rruns [-H host] [-T tmproot] -f rscript name=value... + rruns [-H host] [-T tmproot] -r recipe name=value... + +Lancer ce script sans argument (hors options) est équivalent à le lancer avec +l'argument @default + +OPTIONS + -C Ne pas faire le déploiement. Configurer uniquement la connexion par clé + sur les hôtes distants spécifiés pour le user spécifié. Il faut pouvoir + se connecter par mot de passe pour configurer la connexion par clé. + Si l'on veut configurer la connexion par clé pour le user root, mais que + ce n'est pas possible de se connecter par mot de passe avec le user root + sur l'hôte distant, et qu'il existe un user sudoer sur l'hôte distant, + il est possible de faire la configuration avec '--configure root'. La + commande serait alors + rruns -H user@host --configure root + -T tmproot + Spécifier le répertoire temporaire sur l'hôte distant, comme par exemple + /var/tmp. Cette option est utile pour les vservers, qui ont par défaut + un /tmp minuscule de 16 Mo. + -S ssh + Spécifier le programme à utiliser pour la connection par ssh. + -H host + Spécifier un hôte distant sur lequel faire le déploiement. Plusieurs + options -H peuvent être spécifiées, ou alors on peut séparer plusieurs + hôtes par ':', e.g. -H host1:host2 + Par défaut, la connexion sur l'hôte distant se fait avec l'utilisateur + root. Il est possible de spécifier un autre utilisateur avec la syntaxe + user@host, e.g -H jclain@host + -f RSCRIPT + Lancer le script individuel spécifié au lieu de chercher dans les + répertoires $RUNS{SCRIPTS,HOSTS}PATH + -r RECIPE + Lancer les scripts spécifiés dans le fichier de recettes individuel + spécifié. + -z Forcer la réinstallation des scripts qui se basent sur shouldrun/setdone + -o OUTPUT + Générer l'archive à lancer sur l'hôte distant au lieu de faire le + déploiement. Si plusieurs hôtes sont spécifiés, OUTPUT est considéré + comme un nom de base auquel est ajouté le nom de l'hôte sur lequel + l'archive doit être déployée. + --sysinfos + Après un déploiement réussi sur l'hôte distant, inscrire si ce n'est + déjà fait le résultat de la commande usysinfos dans le fichier + sysinfos.conf du répertoire d'hôte. + Cette option est automatiquement activée si ce script est lancé sans + argument (hors options). +}}} diff --git a/doc/ruinst.twp b/doc/ruinst.twp new file mode 100644 index 0000000..506cbce --- /dev/null +++ b/doc/ruinst.twp @@ -0,0 +1,38 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ruinst + +{{{ +ruinst: Déploiement distant avec uinst + +USAGE + ruinst [-H host] [-T tmproot] [-- options de uinst] + +OPTIONS + -C Ne pas faire le déploiement. Configurer uniquement la connexion par clé + sur les hôtes distants spécifiés pour le user spécifié. Il faut pouvoir + se connecter par mot de passe pour configurer la connexion par clé. + Si l'on veut configurer la connexion par clé pour le user root, mais que + ce n'est pas possible de se connecter par mot de passe avec le user root + sur l'hôte distant, et qu'il existe un user sudoer sur l'hôte distant, + il est possible de faire la configuration avec '--configure root'. La + commande serait alors + ruinst -H user@host --configure root + -T tmproot + Spécifier le répertoire temporaire sur l'hôte distant, comme par exemple + /var/tmp. Cette option est utile pour les vservers, qui ont par défaut + un /tmp minuscule de 16 Mo. + -S ssh + Spécifier le programme à utiliser pour la connection par ssh. + -H host + Spécifier un hôte distant sur lequel faire le déploiement. Plusieurs + options -H peuvent être spécifiées, ou alors on peut séparer plusieurs + hôtes par ':', e.g. -H host1:host2 + Par défaut, la connexion sur l'hôte distant se fait avec l'utilisateur + root. Il est possible de spécifier un autre utilisateur avec la syntaxe + user@host, e.g -H user@host +}}} diff --git a/doc/runs.twp b/doc/runs.twp new file mode 100644 index 0000000..6d5b297 --- /dev/null +++ b/doc/runs.twp @@ -0,0 +1,60 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 30/03/2012 04:42 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: runs + +{{{ +runs: Lancer un script avec le protocole runs + +USAGE + runs [options] rscriptname name=value... + runs [options] @recipe name=value... + runs [options] -f rscript name=value... + runs [options] -r recipe name=value... + +OPTIONS +Configuration + --init + Créer le répertoire d'hôte correspondant à l'hôte courant ou à l'hôte + spécifié avec l'option -h + --sysinfos DATA + Avec l'option --init, initialiser le fichier sysinfos.conf avec DATA, si + le fichier n'a pas déjà été provisionné. Sans cette option, un fichier + vide avec des commentaires est créé à la place. + --create RSCRIPT + Créer un modèle de script avec le nom donné. + Avec l'option -h, le script est créé dans le répertoire d'hôte + correspondant à l'hôte spécifié + +Gestion des scripts + -s Forcer l'exécution du script avec l'utilisateur root si ce n'est pas + déjà le cas + -f RSCRIPT + Lancer le script individuel spécifié au lieu de chercher dans les + répertoires de $RUNSSCRIPTSPATH + -r RECIPE + Lancer les scripts spécifiés dans le fichier de recettes individuel + spécifié. + -h HOSTNAME[.DOMAIN] + Spécifier que les scripts sont destinés à être lancés sur l'hôte + spécifié. Les scripts sont alors aussi cherchés dans les répertoires + {$RUNSHOSTSPATH}/$hostname.$domain (par défaut) et + {$RUNSHOSTSPATH}/$domain/$hostname (le cas échéant) + L'option --host est équivalente, sauf que son argument est facultatif et + que sa valeur par défaut est l'hôte courant, soit melee + --list + Afficher la liste des scripts qui sont disponibles. Avec l'option -h, + inclure aussi les scripts spécifiques à cet hôte. + Avec cette option, les arguments supplémentaires agissent comme des + filtres (regexp utilisée avec l'opérateur == de la commande [[). Les + noms des scripts doivent valider au moins un filtre. + --info + Afficher la la description du script et la valeur de chaque variable + définies + --desc-only + Afficher seulement la description du script + -z Forcer la réinstallation des scripts qui se basent sur shouldrun/setdone +}}} diff --git a/doc/rwoinst.twp b/doc/rwoinst.twp new file mode 100644 index 0000000..b789c56 --- /dev/null +++ b/doc/rwoinst.twp @@ -0,0 +1,38 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: rwoinst + +{{{ +rwoinst: Déploiement distant avec woinst + +USAGE + rwoinst [-H host] [-T tmproot] ... [-- options de woinst] + +OPTIONS + -C Ne pas faire le déploiement. Configurer uniquement la connexion par clé + sur les hôtes distants spécifiés pour le user spécifié. Il faut pouvoir + se connecter par mot de passe pour configurer la connexion par clé. + Si l'on veut configurer la connexion par clé pour le user root, mais que + ce n'est pas possible de se connecter par mot de passe avec le user root + sur l'hôte distant, et qu'il existe un user sudoer sur l'hôte distant, + il est possible de faire la configuration avec '--configure root'. La + commande serait alors + rwoinst -H user@host --configure root + -T tmproot + Spécifier le répertoire temporaire sur l'hôte distant, comme par exemple + /var/tmp. Cette option est utile pour les vservers, qui ont par défaut + un /tmp minuscule de 16 Mo. + -S ssh + Spécifier le programme à utiliser pour la connection par ssh. + -H host + Spécifier un hôte distant sur lequel faire le déploiement. Plusieurs + options -H peuvent être spécifiées, ou alors on peut séparer plusieurs + hôtes par ':', e.g. -H host1:host2 + Par défaut, la connexion sur l'hôte distant se fait avec l'utilisateur + root. Il est possible de spécifier un autre utilisateur avec la syntaxe + user@host, e.g -H user@host +}}} diff --git a/doc/twsync.twp b/doc/twsync.twp new file mode 100644 index 0000000..90883be --- /dev/null +++ b/doc/twsync.twp @@ -0,0 +1,39 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: twsync + +{{{ +twsync: synchroniser un répertoire de wiki avec un tiddlywiki + +USAGE + twsync [options] + +Un répertoire de wiki est un répertoire où chaque page est contenu dans un +fichier avec l'extension .twp +Un tiddlywiki est un fichier html contenant le code de TiddlyWiki et les données +associées. + +OPTIONS + -d wikidir + -f wikifile + Spécifier le répertoire de wiki et le tiddlywiki à traiter. Par défaut, + il s'agit de wiki.html dans le répertoire courant. + -u Importer les pages de wikidir dans le tiddlywiki. Utiliser cette action + quand les pages de wikidir sont modifiées et qu'il faut mettre à jour le + tiddlywiki. + Il s'agit de l'action par défaut + --force + Forcer l'importation des pages même si les tiddlers correspondant sont + plus récents dans le tiddlywiki + Forcer aussi la regénération de wikifile même si aucune modification n'a + été détectée + -e Exporter les tiddlers du tiddlywiki vers wikidir. Utiliser cette action + quand le tiddlywiki a été modifié, et qu'il faut synchroniser wikidir + avec les dernières modifications. + -U Mettre à jour le fichier wikifile avec la dernière version de tiddlywiki + située dans ~/wop/modules/nutools/lib/tiddlywiki/empty.html +}}} diff --git a/doc/ubackup.twp b/doc/ubackup.twp new file mode 100644 index 0000000..f6ae035 --- /dev/null +++ b/doc/ubackup.twp @@ -0,0 +1,24 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ubackup + +{{{ +ubackup: faire une sauvegarde des fichiers + +USAGE + ubackup [options] + +OPTIONS + -l Lister les profils de sauvegarde disponibles. + -p Spécifier le profil de sauvegarde à effectuer. Par défaut, toutes les + sauvegardes sont effectuées. + -u USER + Faire le sauvegarde pour l'utilisateur $USER. Cette option n'est valide + que pour l'utilisateur root. + -n Afficher ce qui doit être fait plutôt que de le faire + -H Arrêter la machine après une sauvegarde REUSSIE. +}}} diff --git a/doc/uconf.twp b/doc/uconf.twp new file mode 100644 index 0000000..2272ed8 --- /dev/null +++ b/doc/uconf.twp @@ -0,0 +1,51 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: uconf + +{{{ +uconf: Activer ou désactiver un paramètre dans un fichier de configuration + +USAGE + uconf [options] config name[=value]... + +OPTIONS + -e Activer le paramètre (par défaut). Si le paramètre existe, mais est + commenté, il est décommenté. Si une valeur est spécifiée pour le + paramètre, le paramètre est modifié dans le fichier en conséquence. + -q Cette option s'utilise avec l'option -e et le type shell. Elle permet + de s'assurer que les valeurs ayant des espaces et/ou des caractères + spéciaux sont quotées + -d Désactiver le paramètre. Le paramètre est commenté s'il existe dans le + fichier + -a Ajouter une valeur à la variable, ou un paramètre avec cette valeur + (suivant le type de fichier de configuration) + -A Ajouter une valeur au tableau, ou un paramètre avec cette valeur + (suivant le type de fichier de configuration) + -t TYPE + Type de fichier de configuration. TYPE peut être sh (par défaut), apache + ou mysql. + shell + les paramètres sont de la forme 'name=value', et les commentaires + débutent par '#'. Ce type peut être utilisé pour tous les fichiers + ayant ces caractéristiques, dont les fichiers de script shell + apache + les paramètres sont de la forme 'name value', et les commentaires + débutent par '#'. Ce type peut être utilisé pour tous les fichiers + ayant ces caractéristiques, dont les fichiers de configuration + apache + mysql, php + les paramètres sont dans des sections nommées de la forme [section], + sont de la forme 'name=value', et les commentaires débutent par '#' + ou ';' + Ce type peut être utilisé pour tous les fichiers ayant ces + caractéristiques, dont les fichiers de configuration de mysql et de + php. Avec ce type, la section est obligatoire. + -s SECTION + Avec le type mysql, préciser la section dans laquelle inscrire le + paramètre. Attention! La section DOIT exister, elle n'est pas créée + automatiquement. +}}} diff --git a/doc/ucrontab.twp b/doc/ucrontab.twp new file mode 100644 index 0000000..d1867ba --- /dev/null +++ b/doc/ucrontab.twp @@ -0,0 +1,90 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ucrontab + +{{{ +ucrontab: Ajouter/Supprimer une ligne dans crontab + +USAGE + ucrontab [options] ctline + +OPTIONS + -a Ajouter la ligne dans le fichier crontab (par défaut) + -r Enlever la ligne dans le fichier crontab + -u user + Spécifier l'utilisateur pour lequel on modifie crontab. Par défaut, + modifier le crontab de root + -H host:hosts... + Modifier le crontab sur les hôtes distants spécifiés. Avec l'hôte '.' ou + 'localhost', la modification est faite sur l'hôte local + Si l'action est -a, un script 'undo-ucrontab.sh' est créé dans le + répertoire courant, qui annule la planification. Il est possible de + lancer ce script le lendemain pour enlever les planifications + installées. + ctline + Ligne de crontab de la forme 'minutes hours days months dows command' + Si la ligne est de la forme halt[@hh:mm], la commande 'shutdown -h now' + est planifiée pour le LENDEMAIN à hh:mm. si hh:mm n'est pas spécifié, + l'arrêt est planifié pour 7:00 + -t hh:mm + Ignorer la partie 'minutes hours days months dows' de ctline, et la + remplacer par une planification à hh:mm le LENDEMAIN. + --dom dayOfMonth + --month month + Spécifier respectivement le jour du mois et le mois (1-12) auquel faire + la planification. Par défaut, les planifications sont effectuées pour le + LENDEMAIN. Il est conseillé de spécifier les deux arguments si le jour + doit être fixé. + -s cmdfile + Spécifier un fichier, dont le contenu est utilisé pour générer le script + qui est planifié. Le fichier doit contenir l'ensemble des commandes à + exécuter. Le script est modifié pour s'autodétruire à la fin de son + exécution. Si ce comportement n'est pas souhaité, il faut rajouter la + commande 'exit 0' à la fin. + -S cmd + Comme -s, mais spécifier le contenu du fichier directement sur la ligne + de commande. + -f hostsfile + Spécifier un fichier qui contient la liste des hôtes sur lesquels faire + les planifications. + Les options -s, -S, -H, -d, -n sont ignorées. L'option --add est valide + jusqu'au premier @group du fichier. Voici le format de ce fichier: + + Un groupe d'hôte débute par une ligne de la forme '@group:adelay:gdelay' + - adelay est le nombre de minutes à laisser passer entre les hôtes du + groupe (par défaut 1) + - gdelay est le nombre de minutes à laisser passer après le traitement + du groupe (par défaut 15) + Les autres lignes sont des hôtes sur lequels planifier l'opération, de + la forme 'host:cmd' + - host est un nom d'hôte pleinement qualifié, sur lequel il est possible + de se connecter par clé. + - cmd est une description de la commande à lancer pour effectuer + l'opération planifiée. Utiliser la syntaxe ' + +OPTIONS + var=value + Spécifier la valeur d'une variable ou d'un préfixe, plutôt que de + laisser uprefix l'autodétecter. Utiliser 'uprefix -l' pour avoir une + liste de préfixes valides. Utiliser 'udir --help-vars' pour avoir une + liste de variables valides pour uinst.sh. + -d /path/to/destdir + Spécifier le répertoire destination. Equivalent à l'option + destdir="/path/to/destdir" + -a (par défaut) Si la source n'est pas spécifiée, déterminer le répertoire + à déployer automatiquement. + --no-auto + Ne pas déterminer automatiquement le répertoire à déployer. + --prefix + (par défaut) Corriger les chemins srcdir et destdir qui commencent par + des préfixes valides. Utiliser 'uprefix -l' pour avoir une liste de + préfixes valides. + --no-prefix + Ne jamais corriger un chemin. + --include-vcs + Inclure les fichiers de VCS dans les fichiers copiés. Par défaut, les + fichiers de VCS sont exclus. + -C Configurer un répertoire pour le déploiement avec uinst +}}} diff --git a/doc/uinst.twp b/doc/uinst.twp new file mode 100644 index 0000000..e72975c --- /dev/null +++ b/doc/uinst.twp @@ -0,0 +1,38 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: uinst + +{{{ +uinst: Déployer en local un fichier, une archive, ou un répertoire + +USAGE + uinst [options] + +OPTIONS + var=value + Spécifier la valeur d'une variable ou d'un préfixe, plutôt que de + laisser uprefix l'autodétecter. Utiliser 'uprefix -l' pour avoir une + liste de préfixes valides. Utiliser 'udir --help-vars' pour avoir une + liste de variables valides pour uinst. + -d /path/to/destdir + Spécifier le répertoire destination. Equivalent à l'option + destdir="/path/to/destdir" + -a (par défaut) Si la source n'est pas spécifiée, déterminer le répertoire + à déployer automatiquement. + --no-auto + Ne pas déterminer automatiquement le répertoire à déployer. + --prefix + (par défaut) Corriger les chemins srcdir et destdir qui commencent par + des préfixes valides. Utiliser 'uprefix -l' pour avoir une liste de + préfixes valides. + --no-prefix + Ne jamais corriger un chemin. + --include-vcs + Inclure les fichiers de VCS dans les fichiers copiés. Par défaut, les + fichiers de VCS sont exclus. + -C Configurer un répertoire pour le déploiement avec uinst +}}} diff --git a/doc/ujava.twp b/doc/ujava.twp new file mode 100644 index 0000000..9437a88 --- /dev/null +++ b/doc/ujava.twp @@ -0,0 +1,28 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ujava + +{{{ +ujava: Lancer un script après avoir sélectionné une version de java + +USAGE + ujava [options] version [args...] + +OPTIONS + -b, --bits 32|64|auto + Sélectionner une version 32 ou 64 bits de java + -e, --exact + Sélectionner la version *exacte* de java demandée, au lieu de la version + minimum correspondant à la version demandée. + Si la version requise de java n'est pas trouvée, retourner avec le code + d'erreur 254. + +La version de java attendue peut-être exprimée de l'une des façons suivantes: +1.4 1.4+ 1.5 1.5+ 1.6 1.6+ 1.7 1.7+ +Si args n'est pas spécifié, un shell est lancé dans lequel les variables +JAVA_HOME, JAVA, JAVAC et PATH sont mis à jour. +}}} diff --git a/doc/uldap.twp b/doc/uldap.twp new file mode 100644 index 0000000..75889a0 --- /dev/null +++ b/doc/uldap.twp @@ -0,0 +1,219 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: uldap + +{{{ +uldap: Shell pour accéder à un serveur ldap + +USAGE + uldap [options] + +OPTIONS + -C profile + Sélectionner un profil de connexion. Par défaut, si l'option -H n'est + pas spécifiée, le premier profil est sélectionné. + -x Ne pas tenter de faire une connexion sur le profil par défaut si aucun + profil n'est sélectionné. + -f script + Lire les commandes depuis le script spécifié. + -n Avec un script donné en ligne de commande ou lu depuis un fichier, ne pas + ajouter automatiquement la commande print à la fin + -i Si un script est spécifié, passer en mode interactif après l'exécution + du script. + -e Forcer l'arrêt du script si une erreur se produit. C'est l'option par + défaut pour un script spécifié avec -f. + -l input.ldif + Charger le fichier input.ldif comme espace de travail initial + -H ldapuri + -D binddn + -w password + -b searchbase + -v var=value + +COMMANDES + $ cmd + Passer directement une commande au shell. + set [options] [var=value...] + Changer des options ou des variables. set sans argument affiche la liste + des variables définies. + [set] plain + Passer en mode 'plain': indiquer que l'espace de travail contient des + données brutes. Les pré-traitements et post-traitements (uncut_on_load, + decode_on_load, encode_on_save, cut_on_save) ne sont pas appliqués sur + cet espace de travail + [set] ldif + Passer en mode 'ldif': indiquer que l'espace de travail contient des + données ldif. Les pré-traitements et post-traitements sont appliqués + normalement sur cet espace de travail + [set] append + Pour certaines opérations, spécifier si le résultat de la *prochaine* + opération remplace le contenu de l'espace de travail courant (par + défaut), ou si le résultat est ajouté à la fin. + last + Afficher en mode édition la dernière commande. Cette commande n'est + fonctionnelle qu'avec une version de bash >=4.x + profile name + Choisir le profil 'name'. Equivalent à 'set profile=name'. Sans + argument, afficher la liste des profils valides. + auth anonymous|binddn [password] + Spécifier le compte à utiliser pour se connecter. Equivalent à + 'set binddn=binddn; set password=password' + clear [-k] + Vider l'espace de travail et passer en mode 'plain'. + Avec l'option -k, supprimer aussi tout l'historique d'annulation. + load [-k] input + Charger un fichier dans l'espace de travail. Si l'extension du fichier + est .ldif, passer en mode 'ldif' + En mode append, rajouter le contenu du fichier à l'espace de travail, + puis repasser en mode replace. + Le code de retour est 0 si le fichier a été chargé, 1 sinon. + save [-a] output + Sauvegarder l'espace de travail dans un fichier. + Avec l'option -a, rajouter au fichier au lieu de l'écraser + print + Afficher l'espace de travail + alias a=rdn... + Définir un alias pour la commande cd. 'a' est l'alias, 'rdn' est le dn + correspondant, exprimé par rapport à $suffix. Sans argument, afficher + la liste des aliases définis. + cd rdn + Changer searchbase. Par défaut, il s'agit d'un rdn relatif à $searchbase + - Certains aliases sont supportés: .. pour l'objet parent, ~ pour + $suffix, / pour la racine. 'cd' sans argument équivaut à 'cd ~' + - Si le dn commence par '~/', il s'agit d'un rdn relatif à $suffix. + - Si le dn commence par /, searchbase reçoit la valeur rdn sans + modifications (sauf bien sûr enlever le '/' de tête si nécessaire). Il + faut alors que ce soit un dn absolu. + ls [-b searchbase] [filter [attrs...]] + search [-b searchbase] [filter [attrs...]] + Utiliser ldapsearch pour faire la recherche, et copier le résultat dans + l'espace de travail. 'ls' est équivalent à 'search -s one'. Si ce n'est + pas déjà le cas, passer en mode 'ldif'. + L'option -b prend une valeur avec la même syntaxe que la commande cd, + sauf que les alias ne sont pas supportés. En particulier, la valeur est + relative au $searchbase courant. Pour faire une recherche par rapport à + $suffix, il faut utiliser la syntaxe ~/searchbase. + En mode append, rajouter le résultat de la recherche à l'espace de + travail, puis repasser en mode replace. + Le code de retour est 1 si aucun enregistrement n'a été trouvé, sinon + le code de retour est celui de la commande ldapsearch. + cut Couper les lignes trop longues. Cette action est en principe effectuée + automatiquement lors de la sauvegarde. Il n'est pas conseillé + d'appliquer des méthodes de transformation après avoir utilisé cette + action. + uncut + Fusionner les lignes coupées. Cette action est en principe effectuée + automatiquement lors du chargement ou après la recherche. + encode [attrs...] + Encoder en base64 les valeurs des attributs mentionnés. + decode [attrs...] + Décoder les valeurs des attributs mentionnés si nécessaire (c'est à dire + s'ils sont encodés en base64) + keepattr attrs... + Garder uniquement les lignes des attributs mentionnés. Ensuite, + supprimer les objets ayant uniquement la ligne dn: (en d'autres termes, + keepattr sans argument supprime *tout* l'espace de travail) + keepval attr patterns... + Pour l'attribut attr, garder uniquement les lignes pour lesquelles les + valeurs correspondent aux expressions régulières. Les autres attributs + ne sont pas modifiés. Ensuite, supprimer les objets ayant uniquement la + ligne dn: + exclude attrs... + Supprimer les lignes des attributs mentionnés. Ensuite, supprimer les + objets ayant uniquement la ligne dn: + excludeval attr patterns... + Pour l'attribut attr, supprimer les lignes pour lesquelles les + valeurs correspondent aux expressions régulières. Les autres attributs + ne sont pas modifiés. Ensuite, supprimer les objets ayant uniquement la + ligne dn: + setval attr values... + Remplacer toutes les valeurs de l'attribut attr par les valeurs + spécifiées. + addval attr values... + Ajouter un nouvel attribut avec les valeurs spécifiées. Si l'attribut + existe déjà, les nouvelles valeurs sont ajoutées à la fin. + sed args + Modifier l'espace de travail avec le résultat de la commande sed. + note: aucun argument n'est filtré, mais il ne faut pas utiliser les + options de sed qui provoquent la modification en place du fichier, + comme par exemple l'option -i + awk args + Modifier l'espace de travail avec le résultat de la commande awk. + grep args + Modifier l'espace de travail avec le résultat de la commande grep. + format [options] attrs... + Formater l'espace de travail en données tabulaires, et passer en mode + 'plain'. + --show-headers + Afficher les en-têtes + -F FSEP + Spécifier le séparateur pour les attributs. Par défaut, il s'agit du + caractère de tabulation. + -R VSEP + Spécifier le séparateur pour les valeurs des attributs. Par défaut, il + s'agit du point-virgule ';' + -e Retourner les valeurs comme des variables shell. Les options -F et -R + sont ignorées. Les attributs multivalués sont écrits sous forme de + tableaux. Par exemple: + attributes=('mail' 'givenName') + index=0 + mail='user@domain.fr' + givenName=('peter' 'gabriel') + --bc + Dans le mode -e, spécifier une commande à insérer avant le premier + enregistrement. Quand cette commande est lancée, index==-1 + -c Dans le mode -e, spécifier une commande à insérer après chaque + enregistrement + --ec + Dans le mode -e, spécifier une commande à insérer après le dernier + enregistrement + sort [args] + Modifier l'espace de travail avec le résultat de la commande sort. + edit + Lancer un éditeur pour modifier l'espace de travail. + diff [options] + Afficher les différences entre l'espace de travail et la version + précédente + ifok cmd + iferror cmd + Si le dernier code de retour est 0 (resp. !=0), lancer la commande cmd + skip n + Sauter les n prochaines commandes. A utiliser avec ifok et iferror + undo + Annuler la dernière modification effectuée sur l'espace de travail + +Les directives suivantes prennent le contenu de l'espace de travail, et le +transforment en une suite de commandes de modifications pour ldapmodify: + + A Créer un objet de toutes pièces avec les attributs donnés et leurs + valeurs. + a Ajouter les valeurs spécifiée à l'attribut + r Remplacer les valeurs de l'attribut par celles spécifiées + d Supprimer les valeurs spécifiées de l'attribut + D Supprimer l'attribut + delentry + Supprimer l'objet + ldapmodify + Utiliser ldapmodify pour modifier les objets sur le serveur. Il faut + utiliser au préalable l'une des méthodes de transformation parmi A, a, + r, d, D, delentry. + Le code de retour est celui de la commande ldapmodify. + ldapadd + Utiliser ldapadd pour créer les objets situés dans l'espace de travail. + Le code de retour est celui de la commande ldapadd. + ldapdelete + Utiliser ldapdelete pour supprimer la liste des dns situés dans l'espace + de travail. + Le code de retour est celui de la commande ldapdelete. + +Notes: +- les expressions régulières sont celles reconnues par awk. +- pour spécifier plusieurs actions sur une même ligne, les séparer par // +- le code de retour est 0 si ok, 255 si une erreur s'est produite (erreur de + syntaxe, de connexion, de lecture/écriture de fichier, etc.). sinon, les + opérations ldap{search,modify,delete,add} ont leur code de retour respectifs +}}} diff --git a/doc/ulib.twp b/doc/ulib.twp new file mode 100644 index 0000000..acd7da3 --- /dev/null +++ b/doc/ulib.twp @@ -0,0 +1,49 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 02/06/2012 09:54 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib + +!Liste des librairies de ulib +* [[ulib/apache]] +* [[ulib/base]] +* [[ulib/bash]] +* [[ulib/compat]] +* [[ulib/conf]] +* [[ulib/crontab]] +* [[ulib/debian]] +* [[ulib/DEFAULTS]] +* [[ulib/install]] +* [[ulib/ipcalc]] +* [[ulib/java]] +* [[ulib/javaproperties]] +* [[ulib/ldap]] +* [[ulib/ldif]] +* [[ulib/legacy]] +* [[ulib/macosx]] +* [[ulib/mkcrypt]] +* [[ulib/modeline]] +* [[ulib/network-manager-service]] +* [[ulib/pkg]] +* [[ulib/prefixes]] +* [[ulib/pretty]] +* [[ulib/runs]] +* [[ulib/service]] +* [[ulib/sysinfos]] +* [[ulib/tiddlywiki]] +* [[ulib/udir]] +* [[ulib/uenv]] +* [[ulib/uenv_update]] +* [[ulib/uinc]] +* [[ulib/uinst]] +* [[ulib/ulib]] +* [[ulib/ulibsh]] +* [[ulib/vcs]] +* [[ulib/virsh]] +* [[ulib/webobjects]] +* [[ulib/woinst]] +* [[ulib/wondermonitor]] +* [[ulib/wosign]] +* [[ulib/wotaskd]] diff --git a/doc/ulib_DEFAULTS.twp b/doc/ulib_DEFAULTS.twp new file mode 100644 index 0000000..bc140b0 --- /dev/null +++ b/doc/ulib_DEFAULTS.twp @@ -0,0 +1,8 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/DEFAULTS + diff --git a/doc/ulib_apache.twp b/doc/ulib_apache.twp new file mode 100644 index 0000000..061cffc --- /dev/null +++ b/doc/ulib_apache.twp @@ -0,0 +1,41 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/apache + +!! {{{get_default_apachebin_prefix}}} +!! {{{get_default_apacheversion_prefix}}} +!! {{{get_default_apachectl_prefix}}} +!! {{{get_default_apachelogdir_prefix}}} +!! {{{get_default_apachesslcertsdir_prefix}}} +!! {{{get_default_apachesslkeysdir_prefix}}} +!! {{{get_default_apacheconfdir_prefix}}} +!! {{{get_default_apacheconf_prefix}}} +!! {{{get_default_apacheavsitesdir_prefix}}} +!! {{{get_default_apachesitesdir_prefix}}} +!! {{{get_default_htdocsdir_prefix}}} +!! {{{get_default_cgibindir_prefix}}} +!! {{{compute_apache_prefixes}}} +!! {{{recompute_apache_prefixes}}} +!! {{{get_APACHEBIN_prefix}}} +!! {{{get_APACHEVERSION_prefix}}} +!! {{{get_APACHECTL_prefix}}} +!! {{{get_APACHELOGDIR_prefix}}} +!! {{{get_APACHESSLCERTSDIR_prefix}}} +!! {{{get_APACHESSLKEYSDIR_prefix}}} +!! {{{get_APACHECONFDIR_prefix}}} +!! {{{get_APACHECONF_prefix}}} +!! {{{get_APACHEAVSITESDIR_prefix}}} +!! {{{get_APACHESITESDIR_prefix}}} +!! {{{get_HTDOCSDIR_prefix}}} +!! {{{get_CGIBINDIR_prefix}}} +!! {{{apache_resolvecert}}} +{{{ +Calculer l'emplacement des certificats correspondant aux arguments $1 et +$2 (qui correspondent aux options --conf et --dir de apache_addcert()), +puis initialiser les variables $3(=cert), $4(=key) et $5(=ca) +}}} +!! {{{apache_addcert}}} diff --git a/doc/ulib_base.twp b/doc/ulib_base.twp new file mode 100644 index 0000000..3c46913 --- /dev/null +++ b/doc/ulib_base.twp @@ -0,0 +1,1043 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 02/06/2012 09:54 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/base + +!! {{{isnum}}} +{{{ +retourner vrai si $1 est une valeur numérique entière (positive ou négative) +}}} +!! {{{ispnum}}} +{{{ +retourner vrai si $1 est une valeur numérique entière positive +}}} +!! {{{isrnum}}} +{{{ +retourner vrai si $1 est une valeur numérique réelle (positive ou négative) +le séparateur décimal peut être . ou , +}}} +!! {{{is_yes}}} +{{{ +retourner vrai si $1 est une valeur "oui" +}}} +!! {{{norm_yes}}} +{{{ +normaliser une valeur vraie: si $1 est une valeur "oui", afficher 1, sinon +afficher une chaine vide +}}} +!! {{{set_yesval}}} +{{{ +mettre la valeur normalisée de la valeur "oui" de $2 dans la variable $1 +}}} +!! {{{is_no}}} +{{{ +retourner vrai si $1 est une valeur "non" +}}} +!! {{{rawecho}}} +{{{ +afficher une valeur brute. contrairement à echo, ne pas reconnaitre les +options -e, -E, -n. +cette fonction est nécessaire pour pouvoir splitter et afficher des options de +ligne de commande. +}}} +!! {{{rawecho_}}} +!! {{{quote_arg}}} +{{{ +Dans la chaine $1, remplacer \ par \\, " par \", $ par \$, ` par \` +Cela permet de quoter une chaine à mettre entre guillements +note: la protection de ! n'est pas effectuée, parce que le comportement du +shell est incohérent entre le shell interactif et les scripts. Pour une +version plus robuste, utiliser plutôt quote_sarg(), qui est malheureusement +plus lent, parce qu'il utilise un programme externe +}}} +!! {{{should_quote}}} +!! {{{quoted_arg}}} +{{{ +Dans la chaine $1, remplacer \ par \\, " par \" et $ par \$, et afficher la +chaine entourée de guillemets, si nécessaire +}}} +!! {{{quote_in}}} +{{{ +Comme quote_arg pour une chaine lue sur stdin +}}} +!! {{{quote_sin}}} +{{{ +Pour la chaine lue sur stdin, remplacer ' par '\''. Cela permet de protéger une +chaine à mettre entre quotes +}}} +!! {{{quote_sarg}}} +{{{ +Dans la chaine $1, remplacer ' par '\''. Cette fonction utilise quote_sin, +puisque le shell a des difficultés à faire le rechercher/remplacer approprié +}}} +!! {{{quoted_sarg}}} +{{{ +Dans la chaine $1, remplacer ' par '\'', et afficher la chaine entourée de +quotes +}}} +!! {{{quoted_args}}} +{{{ +Comme quoted_arg, mais tous les arguments sont quotés et affichés entourés de +guillemets, ce qui permet de construire des arguments d'une ligne de commande +}}} +!! {{{quoted_sargs}}} +{{{ +Comme quoted_sarg, mais tous les arguments sont quotés et affichés entourés de +quotes, ce qui permet de construire des arguments d'une ligne de commande +}}} +!! {{{quote_awk}}} +{{{ +dans la chaine $1, remplacer \ par \\ et " par \". ceci est utile pour quoter +des valeur à insérer dans un script awk +}}} +!! {{{quoted_awk}}} +{{{ +dans la chaine $1, remplacer \ par \\ et " par \" et afficher la +chaine entourée de guillemets. ceci est utile pour quoter +des valeur à insérer dans un script awk +}}} +!! {{{quote_seds}}} +{{{ +Quoter la chaine $1, qui doit être utilisée comme chaine de recherche ou de +remplacement de grep, sed ou awk +}}} +!! {{{quote_form}}} +{{{ +Dans la chaine $1, remplacer '%' par '%25', '+' par '%2B', '&' par '%26', '=' +par '%3D', ' ' par '+' +}}} +!! {{{quoted_form}}} +{{{ +Dans la chaine $1 qui est de la forme "name=value", remplacer dans name et +dans value '%' par '%25', '+' par '%2B', '&' par '%26', '=' par '%3D', ' ' par +'+' +}}} +!! {{{first_char}}} +{{{ +retourner le premier caractère de la chaine $1 +}}} +!! {{{last_char}}} +{{{ +retourner le dernier caractère de la chaine $1 +}}} +!! {{{first_chars}}} +{{{ +retourner tous les caractères de la chaine $1, excepté le dernier +}}} +!! {{{last_chars}}} +{{{ +retourner tous les caractères de la chaine $1, excepté le premier +}}} +!! {{{first_char_is}}} +{{{ +Tester si le premier caractère de la chaine $1 est $2 +}}} +!! {{{last_char_is}}} +{{{ +Tester si le dernier caractère de la chaine $1 est $2 +}}} +!! {{{beginswith}}} +{{{ +Tester si la chaine $1 commence par le wildcard $2 +}}} +!! {{{endswith}}} +{{{ +Tester si la chaine $1 se termine par le wildcard $2 +}}} +!! {{{splitvar}}} +{{{ +Découper $1 de la forme name[=value] entre le nom, qui est placé dans la +variable $2(=name) et la valeur, qui est placée dans la variable $3(=value) +}}} +!! {{{splitname}}} +{{{ +Découper $1 de la forme basename[.ext] entre le nom de base du fichier, qui +est placé dans la variable $2(=basename) et l'extension, qui est placée dans +la variable $3(=ext) +Attention, si $1 est un chemin, le résultat risque d'être faussé. Par exemple, +'splitname a.b/c' ne donne pas le résultat escompté. +}}} +!! {{{splithost}}} +{{{ +Découper $1 de la forme hostname[.domain] entre le nom d'hôte, qui est placé +dans la variable $2(=hostname) et le domaine, qui est placée dans la variable +$3(=domain) +}}} +!! {{{splituserhost}}} +{{{ +Découper $1 de la forme [user@]host entre le nom de l'utilisateur, qui est placé +dans la variable $2(=user) et le nom d'hôte, qui est placée dans la variable +$3(=host) +}}} +!! {{{splitpair}}} +{{{ +Découper $1 de la forme first[:second] entre la première valeur, qui est placé +dans la variable $2(=src) et la deuxième valeur, qui est placée dans la variable +$3(=dest) +}}} +!! {{{splitproxy}}} +{{{ +Découper $1 de la forme http://[user:password@]host[:port]/ entre les valeurs +$2(=host), $3(=port), $4(=user), $5(=password) +}}} +!! {{{set_var_cmd}}} +!! {{{set_var}}} +!! {{{set_var_literal}}} +!! {{{set_array_cmd}}} +{{{ +Afficher la commande permettant d'initialiser le tableau $1 avec les valeurs: +soit du tableau $2, soit de $3..$n si $2=="@" +S'il n'y a que l'argument $1, alors afficher la commande permettant de +recréer le tableau $1 +}}} +!! {{{set_array}}} +{{{ +Soit $1 un tableau à créer. Si $2=="@", créer le tableau $1 avec les valeurs +$3..$n. Sinon, créer le tableau $1 avec les valeurs du tableau $2. +Cette fonction n'existe que comme un pendant de set_var(), mais le véritable +intérêt est la fonction set_array_cmd(). cf array_copy() pour une version plus +efficace de la copie de tableaux +}}} +!! {{{array_count}}} +{{{ +retourner le nombre d'éléments du tableau $1 +}}} +!! {{{array_isempty}}} +{{{ +tester si le tableau $1 est vide +}}} +!! {{{array_new}}} +{{{ +créer un tableau vide dont le nom est $1 +}}} +!! {{{array_add}}} +{{{ +ajouter la valeur $2 au tableau dont le nom est $1 +}}} +!! {{{array_ins}}} +{{{ +insérer la valeur $2 au début du tableau dont le nom est $1 +}}} +!! {{{array_del}}} +{{{ +supprimer *les* valeurs $2 du tableau dont le nom est $1 +}}} +!! {{{array_addu}}} +{{{ +ajouter la valeur $2 au tableau dont le nom est $1, si la valeur n'y est pas +déjà. Retourner vrai si la valeur a été ajoutée +}}} +!! {{{array_set}}} +!! {{{array_insu}}} +{{{ +insérer la valeur $2 au début du tableau tableau dont le nom est $1, si la +valeur n'y est pas déjà. Retourner vrai si la valeur a été ajoutée. +}}} +!! {{{array_fillrange}}} +{{{ +Initialiser le tableau $1 avec les nombres de $2(=1) à $3(=10) avec un step de $4(=1) +}}} +!! {{{array_contains}}} +{{{ +tester si le tableau dont le nom est $1 contient la valeur $2 +}}} +!! {{{array_find}}} +{{{ +si le tableau $1 contient la valeur $2, retourner l'index de la valeur. Si le +tableau $3 est spécifié, retourner la valeur à l'index dans ce tableau +}}} +!! {{{array_reverse}}} +{{{ +Inverser l'ordre des élément du tableau $1 +}}} +!! {{{array_replace}}} +{{{ +dans le tableau $1, remplacer toutes les occurences de $2 par $3..* +}}} +!! {{{array_each}}} +{{{ +Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les +arguments '$v $3..$n' +}}} +!! {{{array_map}}} +{{{ +Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les +arguments '$v $3..$n', et remplacer la valeur par le résultat de la fonction +}}} +!! {{{first_value}}} +{{{ +retourner la première valeur du tableau $1 +}}} +!! {{{last_value}}} +{{{ +retourner la dernière valeur du tableau $1 +}}} +!! {{{array_copy}}} +{{{ +copier le contenu du tableau $2 dans le tableau $1 +}}} +!! {{{array_copy_firsts}}} +{{{ +copier tous les valeurs du tableau $2(=$1) dans le tableau $1, excepté la dernière +}}} +!! {{{array_del_last}}} +!! {{{array_copy_lasts}}} +{{{ +copier tous les valeurs du tableau $2(=$1) dans le tableau $1, excepté la première +}}} +!! {{{array_del_first}}} +!! {{{array_extend}}} +{{{ +ajouter le contenu du tableau $2 au tableau $1 +}}} +!! {{{array_extendu}}} +{{{ +ajouter chacune des valeurs du tableau $2 au tableau $1, si ces valeurs n'y +sont pas déjà. Retourner vrai si au moins une valeur a été ajoutée +}}} +!! {{{array_extend_firsts}}} +{{{ +ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la dernière +}}} +!! {{{array_extend_lasts}}} +{{{ +ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la première +}}} +!! {{{array_split}}} +{{{ +créer le tableau $1 avec chaque élément de $2 (un ensemble d'éléments séparés +par $3, qui vaut ':' par défaut). Les éléments vides sont ignorés. par exemple +"a::b" est équivalent à "a:b" +}}} +!! {{{array_from_path}}} +!! {{{array_from_lines}}} +{{{ +créer le tableau $1 avec chaque ligne de $2. Les lignes vides sont ignorés. +}}} +!! {{{array_join}}} +{{{ +afficher le contenu du tableau dont le nom est $1 sous forme d'une liste de +valeurs séparées par $2 (par défaut, une virgule) +Si $1=="@", alors les éléments du tableaux sont les arguments de la fonction à +partir de $3 +Si $1!="@" et que le tableau est vide, afficher $3 +Si $1!="@", $4 et $5 sont des préfixes et suffixes à rajouter à chaque élément +}}} +!! {{{array_mapjoin}}} +{{{ +map le tableau $1 avec la fonction $2, puis afficher le résultat en séparant +chaque élément par $3. Les arguments et la sémantique sont les mêmes que pour +array_join en tenant compte de l'argument supplémentaire $2 qui est la +fonction pour array_map (les autres arguments sont décalés en conséquence) +}}} +!! {{{array_to_lines}}} +{{{ +afficher le tableau dont le nom est $1 sous forme de lignes +}}} +!! {{{array_to_path}}} +{{{ +afficher le tableau dont le nom est $1 sous forme d'une liste de chemins +séparés par ':') +}}} +!! {{{array_fix_paths}}} +{{{ +Corriger les valeurs du tableau $1. Les valeurs contenant le séparateur +$2(=':') sont séparées en plusieurs valeurs. Par exemple avec le tableau +input=(a b:c), le résultat est input=(a b c) +}}} +!! {{{get_date_rfc822}}} +!! {{{get_date_fr}}} +!! {{{get_time_fr}}} +!! {{{parse_date}}} +!! {{{udelpath}}} +{{{ +supprimer le chemin $1 de $2(=PATH) +}}} +!! {{{uaddpath}}} +{{{ +Ajouter le chemin $1 à la fin, dans $2(=PATH), s'il n'y existe pas déjà +}}} +!! {{{uinspath}}} +{{{ +Ajouter le chemin $1 au début, dans $2(=PATH), s'il n'y existe pas déjà +}}} +!! {{{withpath}}} +{{{ +tester si le chemin est relatif à . ou à .., ou est absolu. i.e 'withpath a/b' +renvoie faux alors que 'withpath ./a/b' renvoie vrai +}}} +!! {{{withext}}} +{{{ +tester si le fichier a une extension +}}} +!! {{{normpath}}} +{{{ +normaliser le chemin $1, qui est soit absolu, soit relatif à $2 (qui vaut +$(pwd) par défaut) +}}} +!! {{{abspath}}} +{{{ +Retourner un chemin absolu vers $1. Si $2 est non nul et si $1 est un chemin +relatif, alors $1 est exprimé par rapport à $2, sinon il est exprimé par +rapport au répertoire courant. +Si le chemin n'existe pas, il n'est pas normalisé. Sinon, les meilleurs +efforts sont faits pour normaliser le chemin. +}}} +!! {{{parentdirs}}} +{{{ +Obtenir la liste de tous les parents du répertoire $2 dans le tableau $1, du +répertoire $2 vers la racine. Si $3 commence par 'r' (comme reverse), l'ordre +est inversé: le tableau contient les répertoire de la racine vers $2. +}}} +!! {{{ppath}}} +{{{ +Dans un chemin *absolu*, remplacer "$HOME" par "~" et "$(pwd)/" par "", afin +que le chemin soit plus facile à lire. Le répertoire courant est spécifié par +$2 ou $(pwd) si $2 est vide +}}} +!! {{{relpath}}} +{{{ +Retourner le chemin relatif de $1 par rapport à $2. Si $2 n'est pas spécifié, +on prend le répertoire courant. Si $1 ou $2 ne sont pas des chemins +absolus, il sont transformés en chemins absolus par rapport à $3. Si $1==$2, +retourner une chaine vide +}}} +!! {{{splitwcs}}} +{{{ +Découper un nom de chemin $1 entre la partie sans wildcards, qui est placée dans +la variables $2(=basedir), et la partie avec wildcards, qui est placée dans la +variable $3(=filespec) +}}} +!! {{{deref}}} +{{{ +Retourner un chemin absolu vers le fichier $1, dans lequel toutes les +composantes "lien symbolique" ont été supprimées. +}}} +!! {{{path_if_test}}} +{{{ +afficher un chemin si le fichier $2 existe (en utilisant l'opérateur $1) dans +l'un des chemins absolus $4..n. si $3==relative, afficher le chemin relatif, +sinon le chemin absolu. note: $3 peut être de la forme relative:path, auquel +cas le chemin affiché est exprimé relativement à path +}}} +!! {{{get_nblines}}} +{{{ +Afficher le nombre de lignes d'un fichier +}}} +!! {{{mktempf}}} +{{{ +générer un fichier temporaire et retourner son nom +}}} +!! {{{mktempd}}} +{{{ +générer un répertoire temporaire et retourner son nom +}}} +!! {{{mkdirof}}} +{{{ +Créer le répertoire correspondant à un fichier +}}} +!! {{{cp_a}}} +{{{ +copier des fichiers en gardant le maximum de propriétés +}}} +!! {{{cp_R}}} +{{{ +copier des fichiers récursivement, en suivant les liens symboliques +}}} +!! {{{quietgrep}}} +{{{ +tester la présence d'un pattern dans un fichier +}}} +!! {{{quietdiff}}} +{{{ +tester si deux fichiers sont identiques +}}} +!! {{{testsame}}} +{{{ +tester si deux fichiers sont identiques/différents +}}} +!! {{{testdiff}}} +!! {{{testnewer}}} +{{{ +test si $2 n'existe pas ou si $1 est plus récent que $2 +}}} +!! {{{ps_all}}} +{{{ +afficher tous les processus avec le maximum d'informations +}}} +!! {{{progexists}}} +{{{ +tester l'existence d'un programme dans le PATH +}}} +!! {{{has_python}}} +{{{ +tester la présence de python +}}} +!! {{{has_gawk}}} +{{{ +tester la présence de gnuawk +}}} +!! {{{is_root}}} +{{{ +tester si on est root +}}} +!! {{{source_ifexists}}} +{{{ +sourcer un fichier s'il existe +}}} +!! {{{is_running}}} +{{{ +tester si un programme dont on donne le PID tourne +}}} +!! {{{sedi}}} +{{{ +Lancer sed sur un fichier en le modifiant en place +}}} +!! {{{fix_mode}}} +{{{ +Si le fichier $1 n'est pas writable, le rendre writable temporairement. Si +nécessaire, le fichier est créé. +Cette fonction s'utilise de cette façon: +mode="$(fix_mode file)" +... +unfix_mode file "$mode" +}}} +!! {{{unfix_mode}}} +{{{ +Restaurer le mode $2 du fichier $1 traité par fix_mode +}}} +!! {{{get_mode}}} +{{{ +Obtenir le mode du fichier $1, en le créant si nécessaire. A utiliser avec +unfix_mode pour restaurer le mode d'un fichier qui a été traité avec un +fichier temporaire intermédiaire +}}} +!! {{{rm_maybe}}} +{{{ +Supprimer les fichiers dont on donne la liste. Si aucun fichier n'est +spécifié, cette fonction est un NOP +}}} +!! {{{cpdir}}} +{{{ +copier un fichier dans un répertoire, ou le contenu d'un répertoire dans un +autre répertoire, que le répertoire source soit un lien symbolique ou +non. Cette fonction existe parce que le comportement de "cp_a src dest" n'est +pas consistant selon les plateformes, surtout si src est un lien symbolique +sur un répertoire: parfois on copie le lien, parfois on copie le contenu du +répertoire, parfois on copie le répertoire... +}}} +!! {{{cpnovcs}}} +{{{ +copier le fichier/répertoire $1 *dans* le *répertoire* $2 avec rsync. Les +options du tableau CPNOVCS_OPTS sont rajoutées aux options standard de rsync. +Si $1 est un répertoire, la copie est faite en ignorant les sous-répertoires +de VCS (.svn, CVS). En ce qui concerne les répertoire de VCS, git aussi est +supporté, mais uniquement s'il est à la racine du transfert. +Si $1 se termine par un '/', c'est le contenu du répertoire qui est copié, pas +le répertoire lui-même. Si rsync n'est pas trouvé sur le système, alors on +fait une copie standard qui inclue les répertoires de VCS. +}}} +!! {{{cpdirnovcs}}} +{{{ +Le pendant de cpdir, mais en ignorant les sous-répertoires de VCS: copier le +contenu du répertoire $1 dans le répertoire $2 +}}} +!! {{{doinplace}}} +{{{ +Filtrer le fichier $1 à travers la commande $2..$*, puis remplacer le fichier +s'il n'y a pas eu d'erreur. Retourner le code d'erreur de la commande. Si $1 +n'est pas spécifié ou vaut -, filtrer l'entrée standard vers la sortie +standard. +La variante doinplacef remplace le fichier quelque soit le code de retour de +la commande. A utiliser avec des commandes comme grep qui peuvent retourner +FAUX s'ils ne trouvent pas le motif +}}} +!! {{{doinplacef}}} +!! {{{stripnl}}} +{{{ +Supprimer les caractères de fin de ligne de la chaine en entrée +}}} +!! {{{nl2lf}}} +!! {{{nl2crlf}}} +!! {{{nl2cr}}} +!! {{{list_all}}} +{{{ +lister les fichiers ou répertoires du répertoire $1, un par ligne +$1=un répertoire dont le contenu doit être listé +$2..@=un ensemble de patterns pour le listage +}}} +!! {{{list_files}}} +{{{ +lister les fichiers du répertoire $1, un par ligne +$1=un répertoire dont le contenu doit être listé. +$2..@=un ensemble de patterns pour le listage +}}} +!! {{{list_dirs}}} +{{{ +lister les répertoires du répertoire $1, un par ligne +$1=un répertoire dont le contenu doit être listé. +$2..@=un ensemble de patterns pour le listage +}}} +!! {{{array_lsall}}} +{{{ +Lister les fichiers avec `list_all $2 $3...`, et les mettre dans le +tableau $1. Le tableau contient les chemins complets, par seulement les +noms comme avec list_all +}}} +!! {{{array_lsdirs}}} +{{{ +Lister les fichiers avec `list_dirs $2 $3...`, et les mettre dans le +tableau $1. Le tableau contient les chemins complets, par seulement les +noms comme avec list_dirs +}}} +!! {{{array_lsfiles}}} +{{{ +Lister les fichiers avec `list_files $2 $3...`, et les mettre dans le +tableau $1. Le tableau contient les chemins complets, par seulement les +noms comme avec list_files +}}} +!! {{{merge_contlines}}} +{{{ +Avec les lignes lues sur stdin, fusionner celles qui se terminent par \ avec +les suivantes. +}}} +!! {{{filter_comment}}} +{{{ +Filtrer un fichier de configuration lu sur stdin en enlevant les commentaires +et les lignes vides. +Avec $1==-m, fusionner les lignes qui se terminent par \ avec les suivantes +Comme filter_conf(), les commentaires doivent être sur une ligne à part. +Contrairement à filter_conf, il n'est pas nécessaire que le caractère '#' soit +en début de ligne: il peut apparaitre après des espaces et des tabulations. De +même, une ligne qui ne contient que des espaces et des tabulations est +considérée comme vide. +}}} +!! {{{filter_conf}}} +{{{ +filtrer un fichier de configuration lu sur stdin en enlevant les commentaires +et les lignes vides. Une ligne n'est considérée commentaire que si '#' est un +première position. Utiliser filter_comment() si les commentaire peuvent +commencer par des caractères espace et tabulation. +Si $1==-m, fusionner les lignes qui se terminent par \ avec les suivantes +}}} +!! {{{is_archive}}} +{{{ +tester si l'extension d'un fichier indique que c'est une archive +}}} +!! {{{extract_archive}}} +{{{ +Extraire le contenu de l'archive $1 dans le répertoire ${2:-.} +}}} +!! {{{get_archive_appname}}} +{{{ +Obtenir le nom probable de l'application ou du framework contenu dans +l'archive $1 +}}} +!! {{{dump_usernames}}} +{{{ +Placer dans le tableau $1 la liste des utilisateurs du système +Cette implémentation consulte /etc/passwd et liste tous les utilisateurs dont +le homedir se trouve dans /home, et dont l'uid est >=500 +}}} +!! {{{resolv_ips}}} +{{{ +Placer dans le tableau $1 la liste des adresses ip correspondant à l'hôte +$2. Utiliser la commande host pour faire la résolution +}}} +!! {{{runscript_as}}} +{{{ +Utiliser bash pour lancer le script $2 avec les arguments $3..$n afin qu'il +tourne avec les droits d'un autre user $1(=root). Si $2=exec, utiliser exec +pour lancer le script et ses arguments qui commencent à partir de $3, ce qui +fait que cette fonction ne retourne pas. +Attention! cette fonction ne teste pas avec si on est déjà le user $1. Il y a +donc un risque de boucle infinie si on ne teste pas le user courant. +}}} +!! {{{runscript_as_root}}} +{{{ +Utiliser bash pour lancer le script $1 avec les arguments $2..$* avec les +droits de root. Si on est déjà en root, le script est simplement lancé. Sinon, +utiliser runscript_as pour lancer le script avec les droits de root. +}}} +!! {{{run_as}}} +{{{ +Relancer le script courant afin qu'il tourne avec les droits d'un autre user +$1(=root) +Attention! cette fonction ne teste pas avec si on est déjà ce user. Il y a +donc un risque de boucle infinie si on ne teste pas le user courant. +Il faut lancer cette fonction avec les arguments du script en cours. Par +exemple:: +run_as root "$@" +Si $2=--noexec, on n'utilise pas la fonction exec, ce qui fait que la fonction +retourne. Sinon, on peut considérer que cette fonction ne retourne jamais +}}} +!! {{{run_as_root}}} +{{{ +relancer le script courant afin qu'il tourne en root si on est pas en déjà +root. Sinon, cette fonction est un nop. +}}} +!! {{{awkdef}}} +{{{ +Afficher un script à insérer au début d'un script awk. Ce script définit dans +une section BEGIN{} les variables donnés en arguments, et avec l'option -f, +des fonctions utiles. Si une valeur ne ressemble pas à une définition de +variable, l'analyse des variables s'arrête et le reste des arguments est +inséré tel quel. Cette fonction peut être utilisée de cette manière: +awk "$(awkdef -f var=value... 'script awk')" +Normalement, les variables définies sont scalaires, avec une syntaxe de la +forme var=var. +Il est possible d'utiliser la syntaxe awk_array[@]=bash_array ou array[@] (qui +est équivalente à array[@]=array) pour initialiser le tableau awk_array, qui +contiendra toute les valeurs du tableau nommé bash_array, avec les indices de +1 à N, N étant le nombre d'éléments du tableau bash_array. La variable +awk_array_count est aussi initialisée, et contient le nombre d'éléments du +tableau +Avec l'option -f, les fonctions suivantes sont définies: +- quote_value(s) permet de quoter une valeur pour le shell. la valeur est +entourée de quotes, e.g: +quote_value("here, \"there\" and 'everywhere'.") +--> 'here, "there" and '\''everywhere'\''.' +- quote_grep(s) permet de quoter une valeur pour un pattern *simple* de +grep. Les caractères suivants sont mis en échappement: \ . [ ^ $ * +- quote_egrep(s) permet de quoter une valeur pour un pattern *étendu* de +grep. Les caractères suivants sont mis en échappement: \ . [ ^ $ ? + * ( ) | { +- mkindices(values, indices) créer le tableau indices qui contient les +indices du tableau values, de 1 à N, et retourner la valeur N. Il faudra +utiliser les valeurs de cette manière: +count = mkindices(values, indices) +for (i = 1; i <= count; i++) { +value = values[indices[i]] +... +} +}}} +!! {{{awkrun}}} +{{{ +wrapper pour lancer awk avec un script préparé par awkdef. Les définitions et +les arguments sont séparés par --, e.g. +awkrun var0=value0 var1=value1 -- input0 input1 +}}} +!! {{{parse_opts}}} +{{{ +Analyser des arguments. Cette fonction doit être appelée avec une description +des options à analyser, suivie des arguments proprement dits. En fonction des +options rencontrées, certaines variables sont mises à jour. +Les arguments de cette fonction sont donc de la forme 'optdescs -- args' +}}} +!! {{{lf_trylock}}} +{{{ +USAGE +lf_trylock [-h max_hours] /path/to/lockfile +OPTIONS +lockfile +fichier qui doit contenir le verrou +-h max_hours +Nombre d'heures (par défaut 4) au bout duquel afficher stale +Sinon, afficher locked +Retourne 0 si le verrou a été placé correctement. Il ne faut pas oublier de +supprimer le fichier. Le mieux est de le faire supprimer automatiquement par +autoclean: +lockfile=... +case "$(lf_trylock "$lockfile")" in +locked) ...;; +stale) ...;; +esac +autoclean "$lockfile" +Sinon, retourner 1 et afficher l'une des deux valeurs suivantes: +- stale si le verrou a déjà été placé, depuis au moins max_hours heures +- locked si le verrou a déjà été placé +- retry si une erreur s'est produite pendant la pose du verrou ou sa +lecture. Cela peut se produire si les droits ne sont pas suffisants pour +écrire dans le répertoire destination, ou si le fichier a été supprimé +avant sa lecture (race-condition). Dans ce dernier cas, reessayer permettra +d'acquérir le verrou +}}} +!! {{{pidfile_set}}} +{{{ +USAGE +pidfile_set [-p pid] /path/to/pidfile +OPTIONS +pidfile +fichier qui doit contenir le pid du script +-p pid +spécifier le pid. par défaut, utiliser $$ +-r si pidfile existe mais que le processus ne tourne plus, faire +comme si le fichier n'existe pas. +Retourner 0 si le pid a été correctement écrit dans le fichier. Ce fichier +sera supprimmé automatiquement en fin de script +Retourner 1 si le fichier existe déjà et que le processus est en train de +tourner. +Retourner 2 si le fichier existe déjà mais que le processus ne tourne plus. +Retourner 10 si autre erreur grave s'est produite (par exemple, s'il manque le +chemin vers pidfile, ou si le fichier n'est pas accessible en écriture.) +}}} +!! {{{pidfile_check}}} +{{{ +USAGE +pidfile_check /path/to/pidfile +OPTIONS +pidfile +fichier qui doit contenir le pid d'un processus +Cette fonction permet de vérifier si le processus associé à un fichier de pid +est en train de tourner. +Retourner 0 si le fichier de pid existe et que le process du pid spécifié est +en train de tourner. Retourner 1 sinon. +Retourner 10 si erreur grave s'est produite (par exemple, s'il manque le +chemin vers pidfile, ou si le fichier n'est pas accessible en écriture.) +}}} +!! {{{echo_}}} +!! {{{isatty}}} +{{{ +tester si STDOUT n'est pas une redirection +}}} +!! {{{in_isatty}}} +{{{ +tester si STDIN n'est pas une redirection +}}} +!! {{{out_isatty}}} +{{{ +tester si STDOUT n'est pas une redirection +}}} +!! {{{err_isatty}}} +{{{ +tester si STDERR n'est pas une redirection +}}} +!! {{{die}}} +!! {{{tooenc}}} +{{{ +Transformer la valeur $1 de l'encoding $2(=$OENC) vers l'encoding de sortie +$3=($UTOOLS_OUTPUT_ENCODING) +}}} +!! {{{uecho}}} +!! {{{tooenc_}}} +{{{ +Transformer la valeur $1 de l'encoding $2(=$OENC) vers l'encoding de sortie +$3=($UTOOLS_OUTPUT_ENCODING) +}}} +!! {{{uecho_}}} +!! {{{toienc}}} +{{{ +Transformer la valeur $1 de $2(=$IENC) vers l'encoding d'entrée +$3(=$UTOOLS_INPUT_ENCODING) +}}} +!! {{{uread}}} +{{{ +Lire une valeur sur stdin et la placer dans la variable $1. On assume que la +valeur en entrée est encodée dans l'encoding d'entrée par défaut +}}} +!! {{{stooenc}}} +{{{ +Transformer la valeur lue sur stdin de $OENC vers l'encoding de sortie par +défaut ($UTOOLS_OUTPUT_ENCODING) +}}} +!! {{{stoienc}}} +{{{ +Transformer la valeur lue sur stdin de $IENC vers l'encoding d'entrée par +défaut ($UTOOLS_INPUT_ENCODING) +}}} +!! {{{elogto}}} +{{{ +Activer UTOOLS_EDATE et rediriger STDOUT et STDERR vers le fichier $1 +Si deux fichiers sont spécifiés, rediriger STDOUT vers $1 et STDERR vers $2 +Si aucun fichier n'est spécifié, ne pas faire de redirection +Si la redirection est activée, forcer l'utilisation de l'encoding UTF8 +Si UTOOLS_ELOG_OVERWRITE=1, alors le fichier en sortie est écrasé. Sinon, les +lignes en sortie lui sont ajoutées +}}} +!! {{{show_error}}} +{{{ +tester respectivement si on doit afficher les messages d'erreur, +d'avertissement, d'information, de debug +}}} +!! {{{show_warn}}} +!! {{{show_info}}} +!! {{{show_debug}}} +!! {{{check_verbosity}}} +!! {{{check_interaction}}} +!! {{{eflush}}} +{{{ +Afficher les messages en attente +}}} +!! {{{eclearp}}} +{{{ +Supprimer les message en attente +}}} +!! {{{eerror}}} +{{{ +Afficher un message d'erreur +}}} +!! {{{ewarn}}} +{{{ +Afficher un message d'avertissement +}}} +!! {{{enote}}} +{{{ +Afficher un message d'information de même niveau qu'un avertissement +}}} +!! {{{ebanner}}} +{{{ +Afficher un message très important encadré, puis attendre 5 secondes +}}} +!! {{{eimportant}}} +{{{ +Afficher un message très important +}}} +!! {{{eattention}}} +{{{ +Afficher un message important +}}} +!! {{{einfo}}} +{{{ +Afficher un message d'information +}}} +!! {{{eecho}}} +{{{ +Afficher un message d'information sans préfixe +}}} +!! {{{eecho_}}} +!! {{{edebug}}} +{{{ +Afficher un message de debug +}}} +!! {{{etitle}}} +{{{ +Afficher le titre $1, qui est le début éventuel d'une section. Les section +imbriquées sont affichées indentées. La section n'est pas terminée, et il faut +la terminer explicitement avec eend, sauf dans certains cas précis: +- Si $2..$* est spécifié, c'est une commande. Lancer la commande dans le +contexte de la section. Puis, la section est automatiquement terminée sauf si +l'option -s est spécifiée, auquel cas la section reste ouverte. Si l'option -p +est spécifiée, eclearp() est appelé pour purger les messages en attente +- Dans le cas contraire, l'option -s est ignorée: la section doit toujours +être terminée explicitement. +La fonction etitled() est comme etitle(), mais le titre n'est pas affiché +immédiatement. L'affichage effectif est effectué dès qu'une fonction e* est +utilisée. Ceci permet, avec la fonction eclearp(), de ne pas afficher de titre +pour une section vide. +}}} +!! {{{etitled}}} +!! {{{estep}}} +{{{ +Afficher la description d'une opération. Cette fonction est particulièrement +appropriée dans le contexte d'un etitle. +Les variantes e (error), w (warning), n (note), i (info) permettent d'afficher +des couleurs différentes, mais toutes sont du niveau info. +}}} +!! {{{estepe}}} +!! {{{estepw}}} +!! {{{estepn}}} +!! {{{estepi}}} +!! {{{estep_}}} +!! {{{estepe_}}} +!! {{{estepw_}}} +!! {{{estepn_}}} +!! {{{estepi_}}} +!! {{{ebegin}}} +{{{ +Afficher le message $1, qui décrit le début d'une opération. Cette fonction +débute une section, qu'il faut terminer avec eend. +Si $2..$* est spécifié, c'est une commande. Lancer la commande dans le +contexte de la section. Puis, la section est terminée automatiquement, sauf si +l'option -s est spécifiée, auquel cas la section reste ouverte. +}}} +!! {{{edot}}} +{{{ +Afficher une étape d'une opération, matérialisée par un point '.' ou une +croix 'x' en cas de succès ou d'erreur. Cette fonction est particulièrement +appropriée dans le contexte d'un ebegin. +}}} +!! {{{edotw}}} +{{{ +Afficher un avertissement comme étape d'une opération, matérialisée par une +lettre 'w' (typiquement de couleur jaune). Cette fonction est particulièrement +appropriée dans le contexte d'un ebegin. +}}} +!! {{{ewait}}} +{{{ +Afficher les étapes d'une opération qui dure, matérialisées par des '+' toutes +les secondes tant que le processus $1 tourne. +A utiliser de cette manière: +ebegin "msg" +cmd & +ewait $! +eend +}}} +!! {{{eend}}} +{{{ +Terminer une section. +Avec l'option -c, remettre à zéro toutes les informations de section +Si la section en cours est un ebegin, afficher la fin de l'opération: [ok] ou +[error] en fonction du code de retour de la dernière commande (ou de $1 si +cette valeur est donnée) +Si la section en cours est un etitle, marquer la fin de la section concernée +par le titre. +}}} +!! {{{ask_yesno}}} +{{{ +Afficher le message $1 suivi de [oN] ou [On] suivant que $2 vaut O ou N, puis +lire la réponse. Retourner 0 si la réponse est vrai, 1 sinon. +Si $1 est une option, elle est utilisée avec check_interaction pour savoir si +on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées +($2=message, $3=default) +Si $2 vaut C, la valeur par défaut est N si on est interactif, O sinon +Si $2 vaut X, la valeur par défaut est O si on est interactif, N sinon +}}} +!! {{{read_value}}} +{{{ +Afficher le message $1 suivi de la valeur par défaut [$3] si elle est non +vide, puis lire la valeur donnée par l'utilisateur. Cette valeur doit être non +vide si $4(=O) est vrai. La valeur saisie est placée dans la variable +$2(=value) +Si $1 est une option, elle est utilisée avec check_interaction pour savoir si +on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées +($2=message, $3=variable, $4=default, $5=required) +En mode non interactif, c'est la valeur par défaut qui est sélectionnée. Si +l'utilisateur requière que la valeur soit non vide et que la valeur par défaut +est vide, afficher un message d'erreur et retourner faux +read_password() est comme read_value(), mais la valeur saisie n'est pas +affichée, ce qui la rend appropriée pour la lecture d'un mot de passe. +}}} +!! {{{read_password}}} +!! {{{simple_menu}}} +{{{ +Afficher un menu simple dont les éléments sont les valeurs du tableau +$2(=options). L'option choisie est placée dans la variable $1(=option) +-t TITLE: spécifier le titre du menu +-m YOUR_CHOICE: spécifier le message d'invite pour la sélection de l'option +-d DEFAULT: spécifier l'option par défaut. Par défaut, prendre la valeur +actuelle de la variable $1(=option) +}}} +!! {{{autoclean}}} +{{{ +Ajouter $1..$n à la liste des fichiers à supprimer à la fin du programme +}}} +!! {{{ac_set_tmpfile}}} +{{{ +Créer un fichier temporaire avec le motif $2, l'ajouter à la liste des +fichiers à supprimer en fin de programme, et mettre sa valeur dans la +variable $1 +}}} +!! {{{ac_set_tmpdir}}} +{{{ +Créer un répertoire temporaire avec le motif $2, l'ajouter à la liste des +fichiers à supprimer en fin de programme, et mettre sa valeur dans la +variable $1 +}}} +!! {{{set_defaults}}} +{{{ +Pour chaque argument, sourcer /etc/default/$arg *et* ~/etc/default/$arg si +ceux-ci existent. *Sinon*, lire $scriptdir/lib/default/$arg si ce fichier +existe +}}} +!! {{{myhost}}} +{{{ +Afficher le nom d'hôte pleinement qualifié, en faisant appel à la commande +hostname. Par comparaison, $MYHOST est fourni par bash. +}}} +!! {{{myhostname}}} +{{{ +Afficher le nom d'hôte sans domaine, en faisant appel à la commande +hostname. Par comparaison, $MYHOSTNAME est fourni par bash. +}}} diff --git a/doc/ulib_bash.twp b/doc/ulib_bash.twp new file mode 100644 index 0000000..fc36bce --- /dev/null +++ b/doc/ulib_bash.twp @@ -0,0 +1,8 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/bash + diff --git a/doc/ulib_compat.twp b/doc/ulib_compat.twp new file mode 100644 index 0000000..b3d310b --- /dev/null +++ b/doc/ulib_compat.twp @@ -0,0 +1,8 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/compat + diff --git a/doc/ulib_conf.twp b/doc/ulib_conf.twp new file mode 100644 index 0000000..aca5c2b --- /dev/null +++ b/doc/ulib_conf.twp @@ -0,0 +1,157 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/conf + +!! {{{conf_enable}}} +{{{ +Dans le fichier de configuration $1, activer les paramètres $2..* +Chaque argument de cette fonction correspond à une directive du fichier de +configuration et doit être de la forme name[=value] +Dans tous les cas, toutes les directives de ce nom sont recherchées et +décommentées. Si value est précisée, les directives sont mises à jour. Si +la directive ne figure pas dans le fichier, elle y est rajoutée à la fin +avec la valeur spécifiée. +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +}}} +!! {{{conf_enableq}}} +{{{ +Comme conf_enable(), mais s'assure que les valeurs sont quotées dans le +fichier. Ceci permet de stocker des valeurs avec des espaces ou des +caractères spéciaux. +}}} +!! {{{conf_disable}}} +{{{ +Dans le fichier de configuration $1, désactiver les paramètres $2..* +Chaque argument de cette fonction correspond à une directive du fichier de +configuration et doit être de la forme name[=value] +Toutes les directives de ce noms sont recherchées et commentées. La valeur +si elle est spécifiée, est ignorée. Si la directive ne figure pas dans le +fichier, c'est un NOP. +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +}}} +!! {{{conf_append}}} +{{{ +Dans le fichier de configuration $1, augmenter les valeurs des variables +correspondant aux paramètres $2..* +Chaque argument de cette fonction correspond à une variable du fichier de +configuration, et doit être de la forme name=value +Une ligne 'name="${name:+$name:}$value"' est générée à la fin du fichier +de configuration. +Par défaut, le séparateur CONF_APPEND_SEP vaut ':', mais il est possible +de changer cette valeur, de façon globale +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +}}} +!! {{{conf_array_append}}} +{{{ +Dans le fichier de configuration $1, augmenter les valeurs des variables +de tableau correspondant aux paramètres $2..* +Chaque argument de cette fonction correspond à une variable du fichier de +configuration, et doit être de la forme name=value +Une ligne name=("${name[@]}" "$value") est générée à la fin du fichier de +configuration +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +}}} +!! {{{conf_check}}} +{{{ +Dans le fichier de configuration $1, tester si tous les paramètres $2..* +sont présents. +Chaque argument de cette fonction correspond à une variable du fichier de +configuration, et doit être de la forme name[=value] +Si une valeur est spécifiée, vérifier que le fichier contient la valeur +correspondante. Sinon, tester uniquement la présence de la directive. +}}} +!! {{{aconf_enable}}} +{{{ +Dans le fichier de configuration $1, activer les paramètres $2..* +Chaque argument de cette fonction correspond à une directive du fichier de +configuration et doit être de la forme name[=value] +Toutes les directives de ce nom sont recherchées et décommentées, et la +valeur mise à jour. Si la directive ne figure pas dans le fichier, elle y +est rajoutée à la fin. A cause du mode opératoire, cette fonction ne +convient pas pour les directives dont le nom peut apparaitre plusieurs +fois dans le fichier +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +}}} +!! {{{aconf_disable}}} +{{{ +Dans le fichier de configuration $1, désactiver les paramètres $2..* +Chaque argument de cette fonction correspond à une directive du fichier de +configuration et doit être de la forme name[=value] +Si la valeur est précisée, la directive correspondant à ce nom et cette +valeur est recherchée et commentée. Sinon, toutes les directives de ce +noms sont recherchées et commentées. Si la directive ne figure pas dans le +fichier, c'est un NOP. +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +}}} +!! {{{aconf_append}}} +{{{ +Dans le fichier de configuration $1, ajouter des directives correspondant +aux paramètres $2..* +Chaque argument de cette fonction correspond à une directive du fichier de +configuration et doit être de la forme name=value +Une ligne '$name $value' est ajoutée à la fin du fichier de configuration +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +}}} +!! {{{aconf_array_append}}} +!! {{{aconf_check}}} +{{{ +Dans le fichier de configuration $1, tester si tous les paramètres $2..* +sont présents. +Chaque argument de cette fonction correspond à une variable du fichier de +configuration, et doit être de la forme name[=value] +Si une valeur est spécifiée, vérifier que le fichier contient la valeur +correspondante. Sinon, tester uniquement la présence de la directive. +}}} +!! {{{mconf_enable}}} +{{{ +Dans le fichier de configuration $1, activer les paramètres $3..* de la +section $2 +Chaque argument de cette fonction correspond à une directive du fichier de +configuration et doit être de la forme name[=value] +Toutes les directives de ce nom sont recherchées et décommentées, et la +valeur mise à jour. Si la directive ne figure pas dans le fichier, elle y +est rajoutée à la fin. A cause du mode opératoire, cette fonction ne +convient pas pour les directives dont le nom peut apparaitre plusieurs +fois dans le fichier +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +Cette fonction nécessite gawk +}}} +!! {{{mconf_disable}}} +{{{ +Dans le fichier de configuration $1, désactiver les paramètres $3..* de la +section $2. +Chaque argument de cette fonction correspond à une directive du fichier de +configuration et doit être de la forme name[=value] +Si la valeur est précisée, la directive correspondant à ce nom et cette +valeur est recherchée et commentée. Sinon, toutes les directives de ce +noms sont recherchées et commentées. Si la directive ne figure pas dans le +fichier, c'est un NOP. +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +Cette fonction nécessite gawk +}}} +!! {{{mconf_append}}} +{{{ +Dans le fichier de configuration $1, ajouter des directives correspondant +aux paramètres $3..* dans la section $2 +Chaque argument de cette fonction correspond à une directive du fichier de +configuration et doit être de la forme name=value +Une ligne '$name = $value' est ajoutée à la fin de la section, qui est +créée si nécessaire à la fin du fichier de configuration +Retourner 0 si une modification a été faite dans le fichier, 1 sinon +Cette fonction nécessite gawk +}}} +!! {{{mconf_array_append}}} +!! {{{mconf_check}}} +{{{ +Dans le fichier de configuration $1, tester si tous les paramètres $3..* +sont présents dans la section $2 +Chaque argument de cette fonction correspond à une variable du fichier de +configuration, et doit être de la forme name[=value] +Si une valeur est spécifiée, vérifier que le fichier contient la valeur +correspondante. Sinon, tester uniquement la présence de la directive. +Cette fonction nécessite gawk +}}} diff --git a/doc/ulib_crontab.twp b/doc/ulib_crontab.twp new file mode 100644 index 0000000..df1132d --- /dev/null +++ b/doc/ulib_crontab.twp @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/crontab + +!! {{{add_to_crontab}}} +!! {{{remove_from_crontab}}} diff --git a/doc/ulib_debian.twp b/doc/ulib_debian.twp new file mode 100644 index 0000000..e10032c --- /dev/null +++ b/doc/ulib_debian.twp @@ -0,0 +1,49 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 30/03/2012 04:43 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/debian + +!! {{{pkg_check}}} +{{{ +Vérifier que les packages sont installés sur le système +}}} +!! {{{pkg_update}}} +{{{ +Mettre à jour la liste des packages silencieusement sans confirmation +}}} +!! {{{pkg_upgrade}}} +{{{ +Mettre à jour la liste des packages silencieusement sans confirmation +}}} +!! {{{pkg_install}}} +{{{ +Installer les packages silencieusement et sans confirmation +}}} +!! {{{pkg_installm}}} +{{{ +Installer les packages silencieusement et sans confirmation +Retourner 0 si au moins un des packages a été installé. Sinon, les +packages n'ont pas été instllés, soit parce qu'ils sont déjà installé, +soit parce qu'il y a eu une erreur. +}}} +!! {{{service_disable}}} +{{{ +Désactiver le service $1 pour qu'il ne se lance pas automatiquement au +démarrage +}}} +!! {{{service_enable}}} +{{{ +Activer le service $1 pour qu'il se lance automatiquement au démarrage +}}} +!! {{{create_bridge}}} +{{{ +Créer un nouveau pont nommé $1 avec les paramètres $2 +Si $2 est vide, sa valeur par défaut est +bridge_ports none +bridge_stp off +bridge_fd 2 +bridge_maxwait 0 +}}} diff --git a/doc/ulib_install.twp b/doc/ulib_install.twp new file mode 100644 index 0000000..b1d58f0 --- /dev/null +++ b/doc/ulib_install.twp @@ -0,0 +1,44 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/install + +!! {{{ensure_exists}}} +{{{ +créer le fichier vide "$1" s'il n'existe pas déjà. retourner vrai si le +fichier a été créé sans erreur +}}} +!! {{{copy_replace}}} +{{{ +Copier de façon inconditionnelle le fichier $1 vers le fichier $2 +}}} +!! {{{copy_new}}} +{{{ +copier le fichier "$1" vers le fichier "$2" +Ne pas écraser le fichier destination s'il existe déjà +Retourner vrai si le fichier a été copié sans erreur +}}} +!! {{{copy_update}}} +{{{ +copier le fichier "$1" vers le fichier "$2", si $2 n'existe pas, ou si $1 +a été modifié par rapport à $2. +Retourner vrai si le fichier a été copié sans erreur. +}}} +!! {{{copy_update_ask}}} +{{{ +Copier ou mettre à jour le fichier $1 vers le fichier $2. +Si le fichier existe déjà, la différence est affichée, et une confirmation +est demandée pour l'écrasement du fichier. +Retourner vrai si le fichier a été copié sans erreur. +}}} +!! {{{copy_tree}}} +{{{ +Copier de façon inconditionnelle l'arborescence $1 dans l'arborescence $2 +}}} +!! {{{link_new}}} +{{{ +S'il $2 n'existe pas, créer le lien symbolique $2 pointant vers $1 +}}} diff --git a/doc/ulib_ipcalc.twp b/doc/ulib_ipcalc.twp new file mode 100644 index 0000000..6257a2e --- /dev/null +++ b/doc/ulib_ipcalc.twp @@ -0,0 +1,54 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/ipcalc + +!! {{{get_random_kvm_macaddr}}} +{{{ +Obtenir une adresse mac au hasard commençant par 52:54:00 pour KVM +}}} +!! {{{ipcalc_splitipmask}}} +{{{ +Découper $1 de la forme ip[/mask] entre l'adresse ip, qui est placé dans +la variable $2(=ip) et le masque, qui est placée dans la variable +$3(=mask) +}}} +!! {{{ipcalc_checkip}}} +{{{ +Vérifier l'adresse ip $1 pour voir si elle est valide. Si l'adresse est +valide, l'afficher. Sinon, retourner 1 +}}} +!! {{{ipcalc_checkmask}}} +{{{ +vérifier le masque de sous-réseau $1 pour voir si elle est valide. Si oui, +afficher le suffixe (0, 8, 16, 24, 32) associé. Sinon retourner 1 +}}} +!! {{{ipcalc_broadcast}}} +{{{ +Calculer l'adresse de broadcast correspondant à l'adresse ip $1. Le masque +de sous-réseau peut-être indiqué dans l'adresse ip avec le suffixe /n ou +/x.x.x.x ou donné dans l'argument $2. Seuls les suffixes 0, 8, 16, 24, 32 +sont supportés. +Retourner 1 si un erreur s'est produite, par exemple si l'adresse ou le +suffixe sont invalides ou non supportés. +}}} +!! {{{ipcalc_gateway}}} +{{{ +Calculer l'adresse du gateway correspondant à l'adresse ip $1, en +considérant que le gateway est la première adresse du réseau. Le masque de +sous-réseau peut-être indiqué dans l'adresse ip avec le suffixe /n ou +/x.x.x.x ou donné dans l'argument $2. Seuls les suffixes 0, 8, 16, 24, 32 +sont supportés. +Retourner 1 si un erreur s'est produite, par exemple si l'adresse ou le +suffixe sont invalides ou non supportés. +}}} +!! {{{ipcalc_match}}} +{{{ +Vérifier si l'adresse $1 correspond au modèle $2, e.g.: +ipcalc_match 10.75.0.23 10/8 --> TRUE +ipcalc_match 10.75.0.23 10.75.0.0/24 --> TRUE +ipcalc_match 10.75.0.23 10.75.0.28 --> FALSE +}}} diff --git a/doc/ulib_java.twp b/doc/ulib_java.twp new file mode 100644 index 0000000..3091073 --- /dev/null +++ b/doc/ulib_java.twp @@ -0,0 +1,29 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/java + +!! {{{select_java}}} +{{{ +sélectionner la version *minimum* de java correspondant à $1 +$1== 1.3|1.3+|1.4|1.4+|1.5|1.5+|1.6|1.6+|1.7|1.7+ +Si $2 est défini, il peut s'agit de 32 ou 64 selon que l'on requière la +version 32bits ou 64 bits +}}} +!! {{{select_java_exact}}} +{{{ +sélectionner la version *exacte* de javac correspondant à $1 +$1== 1.3|1.4|1.5|1.6|1.7 pour une correspondance exacte +$1== 1.3+|1.4+|1.5+|1.6+|1.7+ pour une version minimum +Si $2 est défini, il peut s'agit de 32 ou 64 selon que l'on requière la +version 32bits ou 64 bits +}}} +!! {{{get_default_javahome_prefix}}} +!! {{{get_javaextensions_prefix}}} +!! {{{compute_java_prefixes}}} +!! {{{recompute_java_prefixes}}} +!! {{{get_JAVA_HOME_prefix}}} +!! {{{get_JAVAEXTENSIONS_prefix}}} diff --git a/doc/ulib_javaproperties.twp b/doc/ulib_javaproperties.twp new file mode 100644 index 0000000..1c4f64b --- /dev/null +++ b/doc/ulib_javaproperties.twp @@ -0,0 +1,28 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/javaproperties + +!! {{{read_property}}} +{{{ +Lire la propriété $2 dans le fichier $1, et placer la valeur dans la +variable $3. Si la propriété n'existe pas, prendre la valeur par défaut +$4. Si $3=="", elle est construite à partir de $2 en remplaçant les '.' +par '_' +Retourner 1 si une erreur s'est produite (par exemple si le fichier +n'existe pas ou n'est pas accessible en lecture) +}}} +!! {{{write_property}}} +{{{ +Ecrire la propriété $2 dans le fichier $1 avec la valeur $3. +Retourner 1 si une erreur s'est produite (par exemple si le fichier +n'existe pas ou n'est pas accessible en écriture) +}}} +!! {{{write_properties}}} +{{{ +Ecrire les propriétés $2..* dans le fichier $1. Les propriétés sont de la +forme "name=value" +}}} diff --git a/doc/ulib_ldap.twp b/doc/ulib_ldap.twp new file mode 100644 index 0000000..6fdb9e4 --- /dev/null +++ b/doc/ulib_ldap.twp @@ -0,0 +1,66 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/ldap + +!! {{{get_default_ldapconfdir_prefix}}} +{{{ +Calculer et afficher la valeur par défaut de LDAPCONFDIR, ou une chaine +vide si l'on n'a pas pu le détecter automatiquement. +}}} +!! {{{get_default_ldapowner_prefix}}} +{{{ +Calculer et afficher la valeur par défaut de LDAPOWNER, ou une chaine +vide si l'on n'a pas pu le détecter automatiquement. +}}} +!! {{{compute_ldap_prefixes}}} +!! {{{recompute_ldap_prefixes}}} +!! {{{get_LDAPCONFDIR_prefix}}} +!! {{{get_LDAPOWNER_prefix}}} +!! {{{split_ldapuri}}} +{{{ +spliter le ldapuri $1 en $2(=proto), $3(=host) et $4(=port) +}}} +!! {{{get_suffixes}}} +{{{ +obtenir les suffixes de connexion du serveur avec l'uri $1, un par ligne +retourner 1 si la valeur n'a pas pu être obtenue +}}} +!! {{{get_suffix}}} +{{{ +obtenir le *premier* suffixe du serveur avec l'uri $1 +retourner 1 si la valeur n'a pas pu être obtenue +}}} +!! {{{reldn}}} +!! {{{absdn}}} +{{{ +obtenir le dn absolu correspondant au dn $1, le dn de base étant +$2(=$SUFFIX) +}}} +!! {{{subof}}} +{{{ +tester si le dn absolu $1 est $2 ou un enfant de $2 +}}} +!! {{{rabsdn}}} +{{{ +comme absdn, mais tient compte de la valeur de $3(=$SEARCHBASE) +Si le dn commence par "~/", le dn est relatif à $2(=$SUFFIX) +Si le dn commence par "/", le dn est absolu +Sinon, le dn est relatif à $3 +}}} +!! {{{pdn}}} +{{{ +corriger pour *affichage* un dn *absolu*. pour la racine "", afficher +'/'. pour $2(=$SUFFIX), afficher '~'. sinon, afficher le dn relativement à +$2 +}}} +!! {{{filter_slapdconf}}} +{{{ +Traiter un fichier de configuration slapd.conf en fusionnant les lignes +qui sont découpées. Ceci permet de faire des traitements sur le contenu. +Ce filtre s'utilisera normalement avec filter_conf, e.g.: +result.conf +}}} diff --git a/doc/ulib_ldif.twp b/doc/ulib_ldif.twp new file mode 100644 index 0000000..1465c90 --- /dev/null +++ b/doc/ulib_ldif.twp @@ -0,0 +1,60 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 02/06/2012 09:54 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/ldif + +!! {{{def_match_attr}}} +!! {{{def_match_value}}} +!! {{{uncut_lines}}} +{{{ +reformer les lignes qui sont coupées +}}} +!! {{{cut_lines}}} +{{{ +couper les lignes trop longues +}}} +!! {{{ensure_complete_objects}}} +{{{ +S'assurer que le ldif ne contient que des objets complets (éliminant ainsi +les groupes ayant seulement dn:) +}}} +!! {{{delete_marked_objects}}} +{{{ +Supprimer les objets marqués avec --DELETE--: +}}} +!! {{{dump_ldif}}} +!! {{{tl_addattr}}} +!! {{{tl_modifyattr}}} +!! {{{tl_deleteattr}}} +!! {{{tl_deleteentry}}} +!! {{{tl_keepattr}}} +!! {{{tl_keepval}}} +!! {{{tl_excludeattr}}} +!! {{{tl_excludeval}}} +!! {{{tl_keepvalentry}}} +!! {{{tl_excludevalentry}}} +!! {{{tl_replval}}} +!! {{{tl_addval}}} +!! {{{tl_decode}}} +!! {{{tl_encode}}} +!! {{{tl_format}}} +!! {{{dump_headers}}} +!! {{{get_transform_cmd}}} +{{{ +Créer une liste de commandes bash à évaluer en fonction des arguments: une +suite de commandes séparées par // +Les variables suivantes peuvent être définies en entrée: +_T_inputfile: +Si cette variable est non vide, lire à partir du fichier $_T_inputfile +au lieu de stdin +_T_uncut_before: +faut-il fusionner automatiquement les lignes *avant* de lancer les +commandes. +_T_cut_after: +faut-il découper automatiquement les lignes *après* avoir lancé les +commandes. +}}} +!! {{{transform}}} diff --git a/doc/ulib_legacy.twp b/doc/ulib_legacy.twp new file mode 100644 index 0000000..8dd339c --- /dev/null +++ b/doc/ulib_legacy.twp @@ -0,0 +1,50 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/legacy + +!! {{{file_get_vars}}} +{{{ +lire les variables dans un fichier +}}} +!! {{{file_set_vars}}} +{{{ +écrire les variables dans un fichier. Le fichier *doit exister* +}}} +!! {{{write_all_remaining_vars}}} +!! {{{file_get_properties}}} +{{{ +lire les propriétés d'un fichier de propriété java ou xml +}}} +!! {{{file_set_properties}}} +{{{ +écrire les propriétés d'un fichier de propriété java ou xml +}}} +!! {{{file_get_java_properties}}} +{{{ +lire les propriétés d'un fichier de propriétés java. note: les noms de +propriété java peuvent contenir le caractère "." mais pas les noms de +variable bash. La conversion est faite automatiquement. Par exemple:: +file_get_properties build.properties path.to.package "default value" +charge la valeur de la propriété dans la variable path_to_package +}}} +!! {{{file_set_java_properties}}} +{{{ +écrire des propriétés dans un fichier de propriétés java. +}}} +!! {{{write_all_remaining_vars}}} +!! {{{file_get_xml_properties}}} +{{{ +lire les propriétés d'un fichier de propriétés xml. Limitation: les +propriétés ne doivent pas être continuées sur plusieurs lignes. Les +propriétés doivent être écrites sous la forme:: +propvalue +}}} +!! {{{file_set_xml_properties}}} +{{{ +écrire des propriétés dans un fichier de propriétés java. +}}} +!! {{{write_all_remaining_vars}}} diff --git a/doc/ulib_macosx.twp b/doc/ulib_macosx.twp new file mode 100644 index 0000000..65af8d0 --- /dev/null +++ b/doc/ulib_macosx.twp @@ -0,0 +1,24 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 02/06/2012 09:54 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/macosx + +!! {{{local_shellfix}}} +{{{ +Modifier le compte local $1 pour qu'il utilise bash au lieu de sh +}}} +!! {{{local_usercheck}}} +{{{ +Vérifier si le user local $1 existe +}}} +!! {{{local_useradd}}} +{{{ +Créer le user local $1 +USAGE: local_useradd username [gecos [passwd]] +OPTIONS +-s Créer l'utilisateur avec les droits d'administrateur +-m Créer le home directory +}}} diff --git a/doc/ulib_mkcrypt.twp b/doc/ulib_mkcrypt.twp new file mode 100644 index 0000000..08ca3af --- /dev/null +++ b/doc/ulib_mkcrypt.twp @@ -0,0 +1,9 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/mkcrypt + +!! {{{mkcrypt}}} diff --git a/doc/ulib_modeline.twp b/doc/ulib_modeline.twp new file mode 100644 index 0000000..11b55d1 --- /dev/null +++ b/doc/ulib_modeline.twp @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/modeline + +!! {{{printml}}} +!! {{{addml}}} diff --git a/doc/ulib_network-manager-service.twp b/doc/ulib_network-manager-service.twp new file mode 100644 index 0000000..9add42b --- /dev/null +++ b/doc/ulib_network-manager-service.twp @@ -0,0 +1,18 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/network-manager-service + +!! {{{SERVICE_OVERRIDE_network_manager_stopx}}} +{{{ +désactiver network-manager avant de l'arrêter, ce qui permet de s'assurer +que chaque chaque connexion est arrêtée proprement +}}} +!! {{{SERVICE_OVERRIDE_network_manager_startx}}} +{{{ +cette fonction est le pendant de stopx: penser à relancer network-manager +après avoir démarré le service +}}} diff --git a/doc/ulib_pkg.twp b/doc/ulib_pkg.twp new file mode 100644 index 0000000..acb04ac --- /dev/null +++ b/doc/ulib_pkg.twp @@ -0,0 +1,20 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/pkg + +!! {{{pkg_check}}} +{{{ +Vérifier que les packages sont installés sur le système +Retourner 123 si le système n'est pas supporté, et donc qu'aucune commande +d'installation de package n'est disponible. +}}} +!! {{{pkg_install}}} +{{{ +Installer les packages sans confirmation +Retourner 123 si le système n'est pas supporté, et donc qu'aucune commande +d'installation de package n'est disponible. +}}} diff --git a/doc/ulib_prefixes.twp b/doc/ulib_prefixes.twp new file mode 100644 index 0000000..197a9fa --- /dev/null +++ b/doc/ulib_prefixes.twp @@ -0,0 +1,13 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/prefixes + +!! {{{get_USER_prefix}}} +!! {{{get_HOME_prefix}}} +!! {{{expand_prefix}}} +!! {{{list_prefixes}}} +!! {{{dump_prefixes}}} diff --git a/doc/ulib_pretty.twp b/doc/ulib_pretty.twp new file mode 100644 index 0000000..9e6f08d --- /dev/null +++ b/doc/ulib_pretty.twp @@ -0,0 +1,19 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/pretty + +!! {{{get_color}}} +!! {{{set_verbosity}}} +!! {{{set_interaction}}} +!! {{{show_error}}} +!! {{{show_warn}}} +!! {{{show_info}}} +!! {{{show_debug}}} +!! {{{check_verbosity}}} +!! {{{get_verbosity_option}}} +!! {{{check_interaction}}} +!! {{{get_interaction_option}}} diff --git a/doc/ulib_runs.twp b/doc/ulib_runs.twp new file mode 100644 index 0000000..3fb1b1d --- /dev/null +++ b/doc/ulib_runs.twp @@ -0,0 +1,213 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 02/06/2012 09:54 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/runs + +!! {{{runs_initdir}}} +{{{ +Initialiser le répertoire d'hôte. $1 est un nom d'hôte pleinement +qualifié, et les fichiers sont créés dans le premier répertoire de +RUNSHOSTSDIRS qui convient: si un fichier .udir existe avec un tableau +runs_domains qui contient le domaine de l'hôte spécifié, alors c'est ce +répertoire qui est sélectionné. Sinon, on prend le premier répertoire de +RUNSHOSTSDIRS. +$2 spécifie si le fichier doit être créé avec de l'aide (yes) ou avec le +script minimum (no) +$3 est le contenu à placer dans le fichier sysinfos.conf, s'il n'a pas +déjà été provisionné. +Il faut lancer __runs_setpath avant d'utiliser cette fonction et +RUNSHOSTDIRS ne doit pas être vide +}}} +!! {{{runs_create_rscript}}} +{{{ +Créer un modèle de script. Si $2 est spécifié, c'est un nom d'hôte +pleinement qualifié. Le répertoire d'hôte correspondant *doit* exister. +$3 spécifie si le fichier doit être créé avec de l'aide (yes) ou avec le +script minimum (no) +Si $2!="", il faut lancer __runs_setpath avant d'utiliser cette fonction +et RUNSHOSTDIRS ne doit pas être vide +Le chemin du nouveau script est ajouté au tableau new_rscripts +}}} +!! {{{runs_unsupported_system}}} +{{{ +Afficher un message d'erreur indiquant que le système actuel n'est pas +supporté, et quitter le script +}}} +!! {{{runs_require_sysinfos}}} +{{{ +Vérifier le système actuel avec check_sysinfos(), et afficher un message +d'erreur avec runs_unsupported_system() s'il ne correspond pas à la +requête +}}} +!! {{{runs_find_host}}} +!! {{{runs_add_domain}}} +{{{ +Si $1 est nom d'hôte pleinement qualifié, retourner cette valeur +Sinon, lui rajouter le domaine RUNSDOMAIN +}}} +!! {{{runs_find_hostfile}}} +{{{ +Trouver et afficher le fichier d'hôte $1 dans les répertoires du tableau +$3(=RUNSHOSTSDIRS), pour l'hôte $2(=$RUNSHOST). Retourner 0 en cas de +succès, 1 en cas d'échec. +Si host=$2 est une valeur non vide, la recherche est effectuée dans +{$RUNSHOSTSDIRS}/$host et {$RUNSHOSTSDIRS}/$domain/$hostname. Sinon, +retourner 1, car il faut spécifier un nom d'hôte. +}}} +!! {{{runs_find_datafile}}} +{{{ +Trouver et afficher le fichier de données $1 dans le répertoire $3 s'il +est non vide puis dans les répertoires des tableaux $4(=RUNSSCRIPTSDIRS), +$5(=RUNSMODULESDIRS) et $6(=RUNSHOSTSDIRS), pour l'hôte +$2(=$RUNSHOST). Retourner 0 en cas de succès, 1 en cas d'échec. +- D'abord, si $1 *n'est pas* de la forme "./path" ou "../path", chercher +dans $3. +- Puis si l'hôte est spécifié, chercher dans {$RUNSHOSTSDIRS}/$host et +{$RUNSHOSTSDIRS}/$domain/$hostname. +- Puis chercher dans {$RUNSSCRIPTSDIRS} puis {$RUNSMODULESDIRS}. +- Puis, si $1 est de la forme "./path" ou "../path", chercher dans $3. +- Sinon, retourner 1 +}}} +!! {{{runs_initvars}}} +{{{ +Initialiser les variables RUNSDIR, RUNSSCRIPT, RUNSDIRPATH, +RUNSSCRIPTPATH, RUNSSCRIPTDIR et RUNSSCRIPTNAME pour le script $1. +Les valeurs sont initialisées comme suit: +RUNSSCRIPT="$(abspath "$1")" +RUNSDIR="$2" (le répertoire de $RUNS*PATH dans lequel a été trouvé le +script) +Si $3!="", RUNSDIRPATH="$3" et RUNSSCRIPTPATH="$4" +Sinon, RUNSDIRPATH="$RUNSSCRIPTDIR" et RUNSSCRIPTPATH="$RUNSSCRIPTNAME" +}}} +!! {{{runs_find_scriptfile}}} +{{{ +Trouver sans l'afficher le script $1 dans les répertoires des tableaux +$3(=RUNSSCRIPTSDIRS), $4(=RUNSMODULESDIRS) et $5(=RUNSHOSTSDIRS), en +considérant que le script sera lancé sur l'hôte $2(=$RUNSHOST), et +initialiser les variables RUNSDIR, RUNSSCRIPT, RUNSSCRIPTDIR, +RUNSSCRIPTNAME, RUNSDIRPATH et RUNSSCRIPTPATH. Retourner 0 en cas de +succès, 1 en cas d'échec. +RUNSDIR est le répertoire dans lequel a été trouvé le script (parmi les +valeurs fournies dans les tableaux RUNSSCRIPTSDIRS, RUNSMODULESDIRS, +RUNSHOSTSDIRS), RUNSDIRPATH est le répertoire à partir duquel est exprimé +le chemin du script (i.e RUNSDIRPATH + RUNSSCRIPTPATH == RUNSSCRIPT), +RUNSSCRIPT contient le chemin absolu vers le script, RUNSSCRIPTPATH +contient le chemin du script dans RUNSDIRPATH, RUNSSCRIPTDIR le répertoire +du script, et RUNSSCRIPTNAME le nom du script. +D'abord, si l'hôte est spécifié, chercher dans {$RUNSHOSTSDIRS}/$host et +{$RUNSHOSTSDIRS}/$domain/$hostname. Puis chercher dans {$RUNSSCRIPTSDIRS} +}}} +!! {{{runs_find_scriptfile_reverse}}} +{{{ +Soit le fichier de script $1, exprimée de façon absolue, trouver le +fichier parmi les tableaux $3(=RUNSSCRIPTSDIRS), $4(=RUNSMODULESDIRS) +et $5(=RUNSHOSTSDIRS), en considérant que le script sera lancé sur l'hôte +$2(=$RUNSHOST), puis initialiser les variables RUNSDIR, RUNSSCRIPT, +RUNSSCRIPTDIR, RUNSSCRIPTNAME, RUNSDIRPATH et RUNSSCRIPTPATH. Retourner 0 +en cas de succès, 1 en cas d'échec. +}}} +!! {{{runs_rscript}}} +{{{ +Lancer le fichier $1 comme un script avec les arguments $2..$*. Retourner +la valeur de retour du script. +}}} +!! {{{runs_recipe}}} +{{{ +Lancer les scripts de la recette contenue dans le fichier $1. Arrêter au +premier script qui est en erreur +}}} +!! {{{runs_rscriptpath}}} +{{{ +Lancer le script $1 avec les arguments $2..$*. Le script est cherché dans +les répertoires de RUNSSCRIPTSPATH. Retourner 123 si le script n'est pas +trouvé, sinon retourner la valeur de retour du script. +}}} +!! {{{runs_recipepath}}} +{{{ +Lancer la recette $1. Le fichier de recette est cherché dans les +répertoires de RUNSSCRIPTSPATH. Retourner 123 si le fichier de recette n'a +pas été trouvé, sinon retourner la valeur de retour de runs_recipe() +}}} +!! {{{runs_init}}} +!! {{{runs_initdomains}}} +{{{ +Si ce n'est pas déjà le cas, initialiser RUNSDOMAINS en fonction de +/etc/resolv.conf +}}} +!! {{{runs_inithost}}} +!! {{{runs_initsysinfos}}} +!! {{{runs_initworkdir}}} +!! {{{runs_after_export}}} +{{{ +après l'export, initialiser varsfile avec les valeurs qu'il faut garder +entre le déploiement local et le déploiement distant. +}}} +!! {{{runs_check_runsscript}}} +!! {{{runs_var}}} +{{{ +Initialiser les variables selon les directives données en ligne de +commande. +Les arguments peuvent être une suite de définitions de la forme +'scalar=value', 'scalar!=name', 'array+=value', 'array-=value' ou +'array@=name'. +Sinon, le *dernier* argument peut-être de l'une des formes suivantes: +'array value0 [value1...]' pour initialiser un tableau, +'array+ value0 [value1...]' pour ajouter des valeurs à un tableau, +'array- value0 [value1...]' pour enlever des valeurs à un tableau. +Les formes 'scalar!=value' et 'array@=value' sont des indirections et +permettent d'initialiser la variable avec la valeur d'une autre +variable. L'avantage est que la résolution de la valeur est faite +uniquement lors de l'appel de cette fonction, ce qui est utile avec des +fonction comme 'after -r' +}}} +!! {{{runs_conf}}} +{{{ +Activer les flags $* +}}} +!! {{{runs_set_lang}}} +{{{ +Charger la valeur de LANG depuis l'environnement. La variable LANG est +initialisée +}}} +!! {{{runs_set_proxy}}} +{{{ +Charger la valeur du proxy depuis l'environnement. Les variables +http_proxy, ftp_proxy et no_proxy sont initialisées +}}} +!! {{{runs_check_confs}}} +{{{ +Vérifier l'état courant par rapport aux flags +}}} +!! {{{runs_after}}} +{{{ +Vérifier que ce script est lancé après le scriptpath $1, par rapport à +RUNSSTORY +}}} +!! {{{runs_clvars}}} +{{{ +Traiter les spécifications de variables données en ligne de commande ou +dans un fichier de recettes +}}} +!! {{{runs_indvars}}} +{{{ +Résoudre les valeurs effectives des variables qui sont des indirections +}}} +!! {{{runs_clvars_cmd}}} +{{{ +écrire la ligne de recette correspondant au script $1 et aux variables +$2..$* +}}} +!! {{{runs_loadconfs}}} +!! {{{runs_clearvars}}} +!! {{{runs_action_desc}}} +!! {{{runs_action_dump}}} +!! {{{runs_action_run}}} +!! {{{runs_action_export}}} +!! {{{shouldrun}}} +!! {{{checkdone}}} +!! {{{requiredone}}} +!! {{{setdone}}} +!! {{{resetdone}}} diff --git a/doc/ulib_service.twp b/doc/ulib_service.twp new file mode 100644 index 0000000..8e839fa --- /dev/null +++ b/doc/ulib_service.twp @@ -0,0 +1,33 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/service + +!! {{{service}}} +!! {{{service_start}}} +{{{ +démarrer le service $1 de façon inconditionnelle +}}} +!! {{{service_startm}}} +{{{ +démarrer le service $1 s'il n'est pas déjà démarré +}}} +!! {{{service_stop}}} +{{{ +arrêter le service $1 de façon inconditionnelle +}}} +!! {{{service_stopm}}} +{{{ +arrêter le service $1 s'il n'est pas déjà arrêté +}}} +!! {{{service_reload}}} +{{{ +recharger le service $1 +}}} +!! {{{service_status}}} +{{{ +tester/afficher le status du service $1 +}}} diff --git a/doc/ulib_sysinfos.twp b/doc/ulib_sysinfos.twp new file mode 100644 index 0000000..ee85b1c --- /dev/null +++ b/doc/ulib_sysinfos.twp @@ -0,0 +1,36 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/sysinfos + +!! {{{ensure_sysinfos}}} +{{{ +Essayer de déterminer les valeurs des variables $1(=SYSNAME), $2(=SYSDIST) +et $3(=SYSVER) en fonction des valeurs des autres. Cette fonction est à +utiliser quand on récupère cette information de la part de l'utilisateur, +et qu'il faut compléter +}}} +!! {{{check_sysinfos}}} +{{{ +Tester si le système courant ($MYSYSNAME, $MYSYSDIST, $MYSYSVER, $MYBITS) +correspond à au moins un des arguments. +Les options -s, -d, -v, -b permettent respectivement de vérifier le +système, la distribution, la version et le nombre de bits. Il est possible +de spécifier plusieurs tests à effectuer, e.g.: +check_sysinfos -d debian ubuntu -b 64 +pour tester si l'on est sur une distribution debian ou ubuntu *et* sur un +système 64 bits +Avec l'option -v, il est possible de suffixer la valeur avec + ou - selon +que l'on veut toutes les versions situées après ou avant la version +spécifiée. Attention, à cause d'une limitation de l'implémentation, il +faut alors impérativement filtrer aussi sur la distribution, e.g: +check_sysinfo -d debian -v lenny+ +pour tester si on est en lenny ou en squeeze. +De même, l'option -d accepte aussi de suffixer la valeur avec + ou -, mais +cela n'a actuellement de sens qu'avec les version de MacOS X. Il faut +aussi impérativement filtrer sur le système, e.g: +check_sysinfos -s macosx -d 10.5+ +}}} diff --git a/doc/ulib_tiddlywiki.twp b/doc/ulib_tiddlywiki.twp new file mode 100644 index 0000000..b935b06 --- /dev/null +++ b/doc/ulib_tiddlywiki.twp @@ -0,0 +1,115 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/tiddlywiki + +!! {{{twget_version}}} +{{{ +lire le numéro de version dans le fichier $1 +}}} +!! {{{twdump_header}}} +{{{ +lire et afficher le contenu avant-storeArea du tiddlywiki $1 +}}} +!! {{{twdump_footer}}} +{{{ +lire et afficher le contenu après-storeArea du tiddlywiki $1 +}}} +!! {{{twdump_storeArea}}} +{{{ +lire et afficher le storeArea dans le tiddlywiki $1 +}}} +!! {{{twreplace_storeArea}}} +{{{ +dans le tiddlywiki $1, remplacer le storeArea par le fichier $2 (par défaut, lu sur stdin) +}}} +!! {{{twupgrade}}} +{{{ +mettre à jour le tiddlywiki $1 sur la base du tiddlywiki plus récent $2 +}}} +!! {{{twdate_curtwp}}} +{{{ +obtenir la date courante dans le format "dd/mm/YYYY HH:MM" exprimée dans +l'heure locale +$1 est éventuellement la date exprimée en nombre de secondes depuis +l'epoch, exprimée dans l'heure locale +}}} +!! {{{twdate_tid2twp}}} +{{{ +Transformer $1, une date de la forme "YYYYmmddHHMM" exprimée dans le +timezone UTC en une chaine "dd/mm/YYYY HH:MM" exprimée dans l'heure locale +Si $1 n'est pas dans le bon format, ne rien afficher +}}} +!! {{{twdate_curtid}}} +{{{ +obtenir la date courante dans le format "YYYYmmddHHMM" exprimée dans le +timezone UTC +$1 est éventuellement la date exprimée en nombre de secondes depuis +l'epoch, exprimée dans l'heure locale +}}} +!! {{{twdate_twp2tid}}} +{{{ +Transformer $1, une date de la forme "dd/mm/YYYY HH:MM" exprimée en heure +locale en une chaine "YYYYmmddHHMM" exprimée dans le timezone UTC +Si $1 n'est pas dans le bon format, ne rien afficher +}}} +!! {{{twdump_tiddlers}}} +{{{ +dumper les tiddlers du fichier $1 généré avec twdump_storeArea() sous +forme d'une liste d'appel de fonction '__tiddler_data title creator +modifier created modified tags changecount content' +Les arguments de la fonction sont les valeurs brutes du tiddler, qui ont +simplement été corrigées avec unquote_html() +}}} +!! {{{dump_tiddler}}} +!! {{{twdump_twpage}}} +{{{ +Dumper le contenu de la twpage $1 sous forme d'un appel à une function +'__twpage_data title creator modifier created modified tags changecount +content' +Les arguments de la fonction sont les valeurs brutes de la twpage, sauf +que le champ modified contient toujours la date de dernière modification +du fichier. +}}} +!! {{{twwrite_tiddler}}} +{{{ +Ecrire sur STDOUT le tiddler correspondant aux paramètres sont spécifiés +sur la ligne de commande. Les arguments sont les valeurs brutes prises de +la twpage, telles qu'elles sont générées par twdump_twpage() +}}} +!! {{{twcheck_twpage_modified}}} +{{{ +Vérifier si la twpage $1 peut être écrasée par un tiddler dont la date de +modification est $2, de format "YYYYmmddHHMM" exprimée dans le timezone +UTC +C'est le cas si le fichier $1 n'existe pas, ou a une date de modification +antérieure à $2 +}}} +!! {{{twcheck_twpage_newtwpage}}} +{{{ +Vérifier si la twpage $1 peut être écrasée par la twpage $2 +C'est le cas si le fichier $1 n'existe pas, ou a une date de modification +antérieure à $2 +}}} +!! {{{twwrite_twpage}}} +{{{ +Ecrire dans le répertoire courant le fichier correspondant au tiddler dont +les paramètres sont spécifiés sur la ligne de commande. Les arguments sont +les valeurs brutes prises du tiddler, telles qu'elles sont générées par +twdump_tiddlers() +Retourner 0 si le fichier a été écrasé, 1 s'il n'a pas été écrasé parce +qu'il n'a pas été modifié, 2 s'il n'a pas été écrasé parce qu'il est plus +récent. +Si TW_VERBOSE=1, afficher un message informatif lors de l'export +}}} +!! {{{export_to_twpages}}} +{{{ +Exporter tous les tiddlers du tiddlywiki $1 dans le répertoire $2 +}}} +!! {{{import_from_twpages}}} +{{{ +Remplacer les tiddlers du tiddlywiki $1 par les twpages du répertoire $2 +}}} diff --git a/doc/ulib_udir.twp b/doc/ulib_udir.twp new file mode 100644 index 0000000..7dcd544 --- /dev/null +++ b/doc/ulib_udir.twp @@ -0,0 +1,61 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/udir + +!! {{{udir_check}}} +{{{ +Vérifier si le fichier $1 existe +Si $1 est un répertoire, prendre $1/.udir +}}} +!! {{{udir_create_maybe}}} +{{{ +Si le fichier $1 n'existe pas, le créer comme un template .udir +Si $1 est un répertoire, prendre $1/.udir +}}} +!! {{{udir_dump}}} +{{{ +Dumper toutes les variables définies pour le fichier $1 +Si $1 est un répertoire, prendre $1/.udir +}}} +!! {{{udir_eval}}} +{{{ +Evaluer la commande "$2..$*" dans le contexte des variables définies pour +le répertoire $1. La commande est évaluée dans un sous-shell pour ne pas +polluer l'espace de noms courant. +}}} +!! {{{udir_dump_all}}} +{{{ +Dumper toutes les variables définies pour le répertoire $1 et *tous ses +parents* jusqu'à la racine +}}} +!! {{{udir_eval_all}}} +{{{ +Evaluer la commande "$2..$*" dans le contexte des variables définies pour +le répertoire $1 et *tous ses parents* jusqu'à la racine +}}} +!! {{{udir_parse}}} +{{{ +Dans le fichier $1, lire les noms des variables +Si $1 est un répertoire, prendre $1/.udir +Les noms des variables sont placés dans le tableau $2(=UDIR_VARS), et les noms +des tableaux sont placés dans le tableau $3(=UDIR_ARRAYS) +note: les regex qui sont entre "" au lieu de // le sont à cause d'un bug +de awk sous macosx +}}} +!! {{{udir_update}}} +{{{ +Dans le fichier $1, mettre à jour les variables $2..* +Si $1 est un répertoire, prendre $1/.udir +Chaque argument de cette fonction est de la forme name[=value] +Si value n'est pas précisée, la variable obtient une valeur nulle +(i.e. var=) +Si la variable ne figure pas dans le fichier, elle est rajoutée à la fin +du fichier. +Cette fonction nécessite gawk. +}}} +!! {{{write_unseen}}} +!! {{{udir_edit}}} diff --git a/doc/ulib_uenv.twp b/doc/ulib_uenv.twp new file mode 100644 index 0000000..32af1a1 --- /dev/null +++ b/doc/ulib_uenv.twp @@ -0,0 +1,8 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/uenv + diff --git a/doc/ulib_uenv_update.twp b/doc/ulib_uenv_update.twp new file mode 100644 index 0000000..b681b93 --- /dev/null +++ b/doc/ulib_uenv_update.twp @@ -0,0 +1,26 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/uenv_update + +!! {{{uenv_update_dir}}} +{{{ +Mettre à jour l'ordre de chargement pour le répertoire $1 qui contient des +fichiers de profil pour le shell. L'ordre dans lequel le fichiers de +profil doivent être chargé est écrit dans le fichier $1/.source_in_order +Si $2 est spécifié, il s'agit d'un fichier temporaire utilisé pour les +calculs de l'ordre des chargements. +$3(=$1) est le répertoire de destination. Si $1 est un répertoire de +préparation temporaire, on peut spécifier grâce à $3 quel est le +répertoire final après préparation. +}}} +!! {{{uenv_set_destdirs}}} +!! {{{uenv_sourced_in}}} +{{{ +vérifier que l'un des fichiers $2..$* est sourcé dans $1 +}}} +!! {{{uenv_configure_profiles}}} +!! {{{uenv_install_profiles}}} diff --git a/doc/ulib_uinc.twp b/doc/ulib_uinc.twp new file mode 100644 index 0000000..f0fceae --- /dev/null +++ b/doc/ulib_uinc.twp @@ -0,0 +1,9 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/uinc + +!! {{{uinc}}} diff --git a/doc/ulib_uinst.twp b/doc/ulib_uinst.twp new file mode 100644 index 0000000..8e9f018 --- /dev/null +++ b/doc/ulib_uinst.twp @@ -0,0 +1,19 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/uinst + +!! {{{uinst}}} +{{{ +lancer uinst en déclarant les variables locales, de façon à ne pas polluer +l'environnement de l'appelant. +}}} +!! {{{uinst_nolocal}}} +{{{ +Interface en mode ligne de commande pour uinst. Appeler cette fonction +avec les paramètres de la ligne de commande, e.g.: +uinst_clui "$@" +}}} diff --git a/doc/ulib_ulib.twp b/doc/ulib_ulib.twp new file mode 100644 index 0000000..94b004f --- /dev/null +++ b/doc/ulib_ulib.twp @@ -0,0 +1,33 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/ulib + +!! {{{eerror}}} +!! {{{die}}} +!! {{{uprovided}}} +{{{ +Tester si la ulibrairie $1 a déjà été chargé par urequire +}}} +!! {{{uprovide}}} +{{{ +Spécifier que la ulibrairie $1 a été sourcée, ou prétendre que c'est le +cas. +}}} +!! {{{urequire}}} +{{{ +Sourcer un fichier recherché dans ULIBDIRS +Si le fichier est DEFAULTS, charger base, pretty, sysinfos et compat à la +place +}}} +!! {{{ulib_add}}} +{{{ +Ajouter $1 au chemin de recherche de urequire +}}} +!! {{{ulib_sync}}} +{{{ +Synchroniser les librairies ulib dans le répertoire $1 +}}} diff --git a/doc/ulib_ulibsh.twp b/doc/ulib_ulibsh.twp new file mode 100644 index 0000000..3f674b2 --- /dev/null +++ b/doc/ulib_ulibsh.twp @@ -0,0 +1,15 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/ulibsh + +!! {{{eerror}}} +!! {{{die}}} +!! {{{uprovided}}} +!! {{{uprovide}}} +!! {{{urequire}}} +!! {{{ulib_add}}} +!! {{{ulib_sync}}} diff --git a/doc/ulib_vcs.twp b/doc/ulib_vcs.twp new file mode 100644 index 0000000..2489f11 --- /dev/null +++ b/doc/ulib_vcs.twp @@ -0,0 +1,89 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/vcs + +!! {{{vcs_getvcs_help}}} +!! {{{vcs_getvcs}}} +!! {{{vcs_getroot_help}}} +!! {{{vcs_getroot}}} +!! {{{vcs_getrepos_help}}} +!! {{{vcs_getrepos}}} +!! {{{vcs_geturl_help}}} +!! {{{vcs_geturl}}} +!! {{{vcs_vcs_help}}} +!! {{{vcs_vcs}}} +!! {{{vcs_add_help}}} +!! {{{vcs_add}}} +{{{ +le répertoire de référence est le répertoire du premier fichier ajouté +}}} +!! {{{vcs_remove_help}}} +!! {{{vcs_remove}}} +{{{ +le répertoire de référence est le répertoire du premier fichier supprimé +}}} +!! {{{vcs_copy_help}}} +!! {{{vcs_copy}}} +{{{ +le répertoire de référence est le répertoire de destination +}}} +!! {{{vcs_move_help}}} +!! {{{vcs_move}}} +{{{ +le répertoire de référence est le répertoire de destination +}}} +!! {{{vcs_mkdir_help}}} +!! {{{vcs_mkdir}}} +{{{ +le répertoire de référence est le répertoire du premier répertoire créé +}}} +!! {{{vcs_commit_help}}} +!! {{{vcs_commit}}} +!! {{{vcs_status_help}}} +!! {{{vcs_status}}} +!! {{{vcs_update_help}}} +!! {{{vcs_update}}} +!! {{{vcs_diff_help}}} +!! {{{vcs_diff}}} +!! {{{vcs_tag_help}}} +!! {{{vcs_tag}}} +!! {{{git_getrepos}}} +!! {{{git_geturl}}} +!! {{{git_add}}} +!! {{{git_remove}}} +!! {{{git_copy}}} +!! {{{git_move}}} +!! {{{git_mkdir}}} +!! {{{git_commit}}} +!! {{{git_status}}} +!! {{{git_update}}} +!! {{{git_diff}}} +!! {{{git_tag}}} +!! {{{svn_getrepos}}} +!! {{{svn_geturl}}} +!! {{{svn_add}}} +!! {{{svn_remove}}} +!! {{{svn_copy}}} +!! {{{svn_move}}} +!! {{{svn_mkdir}}} +!! {{{svn_commit}}} +!! {{{svn_status}}} +!! {{{svn_update}}} +!! {{{svn_diff}}} +!! {{{svn_tag}}} +!! {{{cvs_getrepos}}} +!! {{{cvs_geturl}}} +!! {{{cvs_add}}} +!! {{{cvs_remove}}} +!! {{{cvs_copy}}} +!! {{{cvs_move}}} +!! {{{cvs_mkdir}}} +!! {{{cvs_commit}}} +!! {{{cvs_status}}} +!! {{{cvs_update}}} +!! {{{cvs_diff}}} +!! {{{cvs_tag}}} diff --git a/doc/ulib_virsh.twp b/doc/ulib_virsh.twp new file mode 100644 index 0000000..d37a4e1 --- /dev/null +++ b/doc/ulib_virsh.twp @@ -0,0 +1,22 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 30/03/2012 04:43 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/virsh + +!! {{{virsh_filter}}} +{{{ +filtrer une sortie liste de virsh. En pratique, ne prendre que les lignes +non vides à partir de la ligne "----*" +}}} +!! {{{virsh_list}}} +!! {{{virsh_pool_list}}} +!! {{{guess_vm_type}}} +{{{ +Afficher hn, kvm, vmware, virtualbox ou openvz suivant que l'on est +*probablement* respectivement sur une machine physique, une machine +virtuelle kvm, vmware, virtualbox, openvz +XXX pour le moment, seuls openvz, kvm et hn sont supportés +}}} diff --git a/doc/ulib_webobjects.twp b/doc/ulib_webobjects.twp new file mode 100644 index 0000000..c1b9e25 --- /dev/null +++ b/doc/ulib_webobjects.twp @@ -0,0 +1,180 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/webobjects + +!! {{{compute_webobjects_prefixes}}} +!! {{{recompute_webobjects_prefixes}}} +!! {{{get_NEXT_ROOT_prefix}}} +!! {{{get_WOROOT_prefix}}} +!! {{{get_LOCALROOT_prefix}}} +!! {{{get_SYSTEMFRAMEWORKS_prefix}}} +!! {{{get_WOEXTENSIONS_prefix}}} +!! {{{get_WOFRAMEWORKS_prefix}}} +!! {{{get_WOAPPLICATIONS_prefix}}} +!! {{{get_WOCONFIGURATION_prefix}}} +!! {{{get_WOAUTOSTART_prefix}}} +!! {{{get_WOLOGS_prefix}}} +!! {{{get_WOVERSION_prefix}}} +!! {{{is_wobundle}}} +{{{ +Tester si $1 a un nom de bundle valide, c'est à dire avec l'extension .woa +ou .framework +}}} +!! {{{is_woappdir}}} +{{{ +Tester si $1 est un répertoire d'application webobjects. Le test est +effectué sur le contenu du bundle, pas sur le nom (utiliser is_wobundle() +pour cela) +}}} +!! {{{is_wofwkdir}}} +{{{ +Tester si $1 est un répertoire de framework webobjects. Le test est +effectué sur le contenu du bundle, pas sur le nom (utiliser is_wobundle() +pour cela) +}}} +!! {{{get_app_winclspth}}} +{{{ +calculer la valeur de Contents/Windows/CLSSPATH.txt pour l'application $1 +}}} +!! {{{get_infofile}}} +{{{ +Obtenir le chemin vers le fichier Info.plist dans le répertoire de +resource du bundle $1 +}}} +!! {{{read_infofile}}} +{{{ +Lire la version et le numéro de release dans le fichier $1 (chemin vers +Info.plist) et les placer dans les variables $2(=version) et $3(=release) +Retourner 1 si un erreur s'est produite, par exemple si le fichier $1 +n'existe pas ou n'est pas accessible en lecture +}}} +!! {{{write_infofile}}} +{{{ +Ecrire $2 (la version) et $3 (le numéro de release) dans le fichier $1 +(chemin vers Info.plist) +Retourner 1 si un erreur s'est produite, par exemple si le fichier $1 +n'existe pas +}}} +!! {{{get_jawotoolsfile}}} +{{{ +Obtenir le chemin vers le fichier jawotools.properties dans le bundle $1 +}}} +!! {{{read_jawotoolsfile}}} +{{{ +lire le fichier de propriété $1 et placer les valeurs dans les variables +$2(=version), $3(=releaseDate), $4(=description) +}}} +!! {{{save_jawotoolsfile}}} +{{{ +écrire le fichier de propriété $1 avec les valeurs version ($2), +releaseDate ($3) et description ($4) +}}} +!! {{{get_versionfile}}} +{{{ +Obtenir le chemin vers le fichier VERSION.txt dans le répertoire de +resource du bundle $1 +}}} +!! {{{get_configfile}}} +{{{ +obtenir le chemin vers le fichier de configuration du répertoire de +resource du bundle +$1=bundle ou resdir (appdir/Contents/Resources ou fwkdir/Resources) +}}} +!! {{{searchreplace_classpath}}} +{{{ +Dans les fichiers classpath de l'application $1, remplacer $2 par $3. Si +$3 est vide, la ligne est supprimée +}}} +!! {{{dump_jars}}} +{{{ +Afficher les jars des frameworks utilisés par l'application $1 +}}} +!! {{{dump_frameworks}}} +{{{ +Afficher les frameworks utilisés par l'application $1 +}}} +!! {{{remove_framework}}} +{{{ +supprimer le framework $2 (nom de base) des fichiers de classpath du +bundle d'application $1 +}}} +!! {{{add_framework}}} +{{{ +s'il n'y existe pas déjà, ajouter le framework $2 (nom de base ou chemin +absolu) aux fichiers de classpath du bundle d'application $1 +}}} +!! {{{fix_jars_case}}} +{{{ +Vérifier que la casse des jars de tous les frameworks utilisés par +l'application $1 est conforme au système de fichier +}}} +!! {{{verifix_bundle}}} +{{{ +vérifier et corriger le bundle $1. Pour une application, on vérifie que le +script est exécutable. Pour un framework, on vérifie que le framework est +conforme au modèle des framework générés par WebObjects. +}}} +!! {{{compute_fapps}}} +{{{ +Placer dans le tableau $1(=fappnames) la liste des noms de bundle +d'applications qui dépendent du framework $2 +Cette opération est faite à partir des informations sur le système de +fichier. Elle ne peut donc concerner qu'une installation locale. +}}} +!! {{{woraurl}}} +{{{ +Faire une requête avec la méthode $1 sur l'url $2 avec le payload $3 (par +exemple pour la méthode POST). la réponse est disponible dans le fichier +$WORAURL_DATA, $4(=http_code) contient le code de réponse. +Retourner 0 en cas de succès, ou une valeur différente de zéro si un +erreur se produit (typiquement, 3 pour une erreur du serveur, 1 pour une +réponse applicative, comme par exemple si l'application n'existe pas) +Les codes de réponse 2xx et 417 sont des succès +Les autres codes (à priori 4xx ou 5xx) sont des erreurs +note: le code 417 est utilisé par le moniteur pour répondre "Non", par +opposition à 200 utilisé pour répondre "OUI" +}}} +!! {{{wogeturl}}} +!! {{{splitins}}} +{{{ +Analyser le nom $1, qui peut être de forme '', 'Name.woa', +'Name.framework', 'App' ou 'Instance-N' (où N est un nombre), et +initialiser les variables $2(=type) et $3(=name) +Si $1=="", type=all et name="" +Si $1==Name.woa, type=woa et name=Name.woa +Si $1==Name.framework, type=fwk et name=Name.framework +Si $1==App, type=app et name=App +si $1==App-N, type=ins et name=App-N +}}} +!! {{{create_wodirs_maybe}}} +!! {{{check_autostart}}} +{{{ +vérifier la présence du fichier $WOAUTOSTART. Si ce n'est pas le cas, le +créer avec le contenu du tableau $1 +}}} +!! {{{get_autostart_order}}} +{{{ +Initialiser le tableau $1 avec la liste donnée dans le fichier +$WOAUTOSTART +}}} +!! {{{apply_autostart_order}}} +{{{ +Réordonner les valeurs $3..* selon la liste donnée dans le tableau $2, +puis placer le résultat dans le tableau $1. $2 doit être construit avec +get_autostart_order(). Si $2 n'est pas spécifié, la liste est construite +localement. +Si le tableau contient des lignes de délai @N, replacer les délais après +les applications appropriées +}}} +!! {{{wotaskd_stop}}} +!! {{{wotaskd_start}}} +!! {{{javamonitor_stop}}} +!! {{{womonitor_stop}}} +!! {{{javamonitor_start}}} +!! {{{womonitor_start}}} +!! {{{woservices_stop}}} +!! {{{woservices_start}}} diff --git a/doc/ulib_woinst.twp b/doc/ulib_woinst.twp new file mode 100644 index 0000000..a13d240 --- /dev/null +++ b/doc/ulib_woinst.twp @@ -0,0 +1,12 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/woinst + +!! {{{date2version}}} +!! {{{woconf}}} +!! {{{wotag}}} +!! {{{woinst}}} diff --git a/doc/ulib_wondermonitor.twp b/doc/ulib_wondermonitor.twp new file mode 100644 index 0000000..9c18728 --- /dev/null +++ b/doc/ulib_wondermonitor.twp @@ -0,0 +1,219 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/wondermonitor + +!! {{{wom__statistics}}} +{{{ +Afficher les statistiques pour le serveur $1, avec éventuellement le mot +de passe $2 +}}} +!! {{{wom__info}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et afficher des informations sur l'application $1 (par défaut, all) +}}} +!! {{{wom__info_filter}}} +{{{ +filtrer le résultat de wom__info en ne gardant que les tags +name, state, activeSessions, autoRecover, deaths, host, port +}}} +!! {{{wom_info}}} +{{{ +Contacter le moniteur sur l'hôte $3, avec éventuellement le mot de passe +$4, et initialiser le tableau $1 avec une liste de valeurs quotés de la +forme: +"'name' 'state' 'activeSessions' 'autoRecover' 'deaths' 'host' 'port'" +concernant l'application $2 (par défaut, toutes les applications). Notez +qu'il y a une ligne par instance d'application +Ces valeurs peuvent être utilisées comme arguments d'une fonction. par +exemple: +wom_info appinfos "" host pw +for args in "${appinfos[@]}"; do +eval "userfunc $args" +done +}}} +!! {{{wom_getValidAndRunning}}} +{{{ +Placer la liste des applications valides dans le tableau $1(=valid_apps) +et la liste des applications qui tournent dans le tableau +$2=(running_apps), en contactant le moniteur sur l'hôte $3, avec +éventuellement le mot de passe $4. +}}} +!! {{{show_appinfo}}} +{{{ +Afficher des informations sur une application. Les arguments doivent être +le résultat de la fonction wom_info() +}}} +!! {{{wom_running}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et tester si l'application $1 (par défaut, all) tourne actuellement +}}} +!! {{{wom_start}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et démarrer l'application $1 (par défaut, all) +}}} +!! {{{wom_stopped}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et tester si l'application $1 (par défaut, all) est actuellement arrêtée +}}} +!! {{{wom_stop}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et arrêter l'application $1 (par défaut, all) +}}} +!! {{{wom_forceQuit}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et forcer l'arrêt de l'application $1 (par défaut, all) +}}} +!! {{{wom_turnScheduledOn}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et activer le flag scheduled sur l'application $1 (par défaut, all) +}}} +!! {{{wom_turnScheduledOff}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et désactiver le flag scheduled sur l'application $1 (par défaut, all) +}}} +!! {{{wom_turnRefuseNewSessionOn}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et activer le flag refuseNewSession sur l'application $1 (par défaut, +all) +}}} +!! {{{wom_turnRefuseNewSessionOff}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et désactiver le flag refuseNewSession sur l'application $1 (par +défaut, all) +}}} +!! {{{wom_turnAutoRecoverOn}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et activer le flag autoRecover sur l'application $1 (par défaut, all) +}}} +!! {{{wom_turnAutoRecoverOff}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et désactiver le flag autoRecover sur l'application $1 (par défaut, +all) +}}} +!! {{{wom_bounce}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et redémarrer l'application $1 (par défaut, all) en mode bounce +}}} +!! {{{wom_clearDeaths}}} +{{{ +Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3, et effacer le compte des morts suspectes pour l'application $1 (par +défaut, all) +}}} +!! {{{wom__getApplications}}} +{{{ +Obtenir des information sur la définition de l'application $1 (ou de +toutes les applications si $1=="") en contactant le moniteur sur l'hôte $2 +avec éventuellement le mot de passe $3. Le résultat est un flux xml, +chaque application étant défini dans un tag . Si un erreur +se produit, l'erreur est dans un tag +}}} +!! {{{wom__getApplications_filter}}} +{{{ +filtrer le résultat de wom__getApplications en ne gardant que les tags +name, unixPath, macPath, winPath +}}} +!! {{{wom_getApplications}}} +{{{ +Obtenir la liste des applications définies en contactant le moniteur sur +l'hôte $3 avec éventuellement le mot de passe $4, et initialiser le +tableau $1 avec une liste de valeurs quotées de la forme: +"'name' 'unixPath' 'macPath' 'winPath'" +concernant l'application $2 (par défaut, toutes les applications) +Ces valeurs peuvent être utilisées comme arguments d'une fonction. par +exemple: +wom_getApplications appinfos "" host pw +for args in "${appinfos[@]}"; do +eval "userfunc $args" +done +}}} +!! {{{wom_addApplication}}} +{{{ +Ajouter une application nommée $1 en contactant le moniteur sur l'hôte $2, +avec éventuellement le mot de passe $3. +Soit le nom Name, par défaut l'exécutable se trouve dans +WOAPPLICATIONS/Name.woa/Name et les logs dans /var/log/WebObjects, et le +flag autoRecover est activé +XXX supporter la possibilité de modifier les valeurs par défaut +}}} +!! {{{wom_addInstance}}} +{{{ +Ajouter une instance sur localhost pour l'application nommée $1 en +contactant le moniteur sur l'hôte $2, avec éventuellement le mot de passe +$3. +XXX supporter la possibilité de modifier les valeurs par défaut +}}} +!! {{{check_compute_apps_localhost}}} +{{{ +si les arguments de compute_apps contiennent des bundles de framework, il +faut avoir accès au système de fichier local. vérifier si l'un des +arguments $2..* est un framework. si c'est le cas, vérifier que l'hôte $1 +est localhost. +retourner 0 si c'est ok, 1 s'il y a des frameworks et que host n'est pas +localhost +}}} +!! {{{compute_apps}}} +{{{ +Remplir le tableau $1(=apps) avec la liste des applications correspondant +aux arguments $3...* +Un bundle de framework (Name.framework) est remplacé par la liste des +bundles d'applications qui dépendent de ce framework. Cette information +est obtenue en consultant le système de fichier local. +Un bundle d'application est remplacé par la liste des applications qui +sont définies pour ce bundle. Cette information est obtenue en consultant +le tableau généré par wom_getApplications(), dont le nom est $2 +Les arguments de la forme @N sont ignorés, ils correspondent à des délais +à respecter lors du démarrage de l'application +}}} +!! {{{get_error_msg}}} +!! {{{start_apps}}} +{{{ +Démarrer les applications $3..$* en contactant le moniteur sur l'hôte $1 +avec le mot de passe éventuel $2 +Les variables globales enable_autorecover et force_enable_autorecover +permettent respectivement d'activer l'autoRecover après le démarrage de +l'application et de forcer l'activation de l'autoRecover même si +l'instance tournait déjà. +Un argument de la forme @N provoque une attente de N secondes. Ceci permet +de placer un temps d'attente entre le démarrage de certaines applications. +}}} +!! {{{stop_apps}}} +{{{ +Arrêter les applications $3..$* en contactant le moniteur sur l'hôte $1 +avec le mot de passe éventuel $2 +Les variables globales disable_autorecover et force_disable_autorecover +permettent respectivement de désactiver l'autoRecover après l'arrêt de +l'application et de forcer la désactivation de l'autoRecover même si +l'instance ne tournait pas. +L'option {-a ARRAY} permet de remplir ARRAY avec la liste des applications +qui ont été effectivement arrêtées. Cette option si elle est spécifiée +doit être en premier +Pour compatibilité avec start_apps, les arguments de la forme @N sont +ignorés. Il n'y a pas de temps d'attente entre les applications lors de +l'arrêt. +}}} +!! {{{bounce_apps}}} +{{{ +Redémarrer les applications $3..$* en mode bounce en contactant le +moniteur sur l'hôte $1 avec le mot de passe éventuel $2 +Pour compatibilité avec start_apps, les arguments de la forme @N sont +ignorés. Il n'y a pas de temps d'attente entre les applications lors du +redémarrage. +}}} diff --git a/doc/ulib_wosign.twp b/doc/ulib_wosign.twp new file mode 100644 index 0000000..34cce91 --- /dev/null +++ b/doc/ulib_wosign.twp @@ -0,0 +1,19 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/wosign + +!! {{{wosign_setup_maybe}}} +!! {{{wosign_jar}}} +!! {{{wosignable}}} +!! {{{wosign}}} +{{{ +Signer un bundle, les jars d'un répertoire, ou un jar +L'option -f force la resignature des jars d'un répertoire ou d'un +bundle. Elle force aussi la signature d'un jar, même s'il semble qu'il +soit la version signée d'un autre jar +on présuppose que wosignable a retourné true +}}} diff --git a/doc/ulib_wotaskd.twp b/doc/ulib_wotaskd.twp new file mode 100644 index 0000000..0d32155 --- /dev/null +++ b/doc/ulib_wotaskd.twp @@ -0,0 +1,12 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:15 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulib/wotaskd + +!! {{{wot_config}}} +{{{ +Afficher la configuration de wotaskd +}}} diff --git a/doc/ulibshell.twp b/doc/ulibshell.twp new file mode 100644 index 0000000..65f9002 --- /dev/null +++ b/doc/ulibshell.twp @@ -0,0 +1,23 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulibshell + +{{{ +ulibshell: Lancer un shell après avoir chargé des modules de ulib + +USAGE + ulibshell [options] [args...] + +OPTIONS + -r module + Spécifier un module à charger avec urequire. Plusieurs modules peuvent + être spécifiés en les séparant par ':' + +Un shell est lancé dans lequel les modules spécifiés sont chargés. Par défaut, +seul le module DEFAULTS est chargé. Les arguments sont passés inchangés au +shell. +}}} diff --git a/doc/ulibsync.twp b/doc/ulibsync.twp new file mode 100644 index 0000000..d4b6811 --- /dev/null +++ b/doc/ulibsync.twp @@ -0,0 +1,18 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: ulibsync + +{{{ +ulibsync: Copier les librairies ulib et/ou pyulib + +USAGE + ulibsync [options] destdir + +OPTIONS + -u Copier ulib + -p Copier pyulib +}}} diff --git a/doc/umatch.twp b/doc/umatch.twp new file mode 100644 index 0000000..dac891a --- /dev/null +++ b/doc/umatch.twp @@ -0,0 +1,29 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: umatch + +{{{ +umatch: Afficher le résultat d'une recherche par regexp + +USAGE + umatch [options] + +Chaque ligne de stdin est matchée pas rapport à l'expression regulière avec la +syntaxe de awk. S'il y a correspondance, afficher soit toute l'expression +matchée, soit chaque groupe s'il y a des expressions parenthésées, chacun des +groupes étant séparé par le caractère sep. + +regexp peut avoir l'une des valeurs suivantes, qui correspondent à des +expressions régulières prédéfinies: + +ip --> [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ + +OPTIONS + -F sep + Spécifier le caractère séparateur s'il y a des champs multiples. Par + défaut, utiliser le caractère ':' +}}} diff --git a/doc/umirror.twp b/doc/umirror.twp new file mode 100644 index 0000000..9ea757c --- /dev/null +++ b/doc/umirror.twp @@ -0,0 +1,17 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: umirror + +{{{ +umirror: faire un miroir d'un site web + +USAGE + umirror [options] url [wget_options] + +OPTIONS + -l Convertir les liens pour consultation locale +}}} diff --git a/doc/update.sh b/doc/update.sh new file mode 100755 index 0000000..8d8a958 --- /dev/null +++ b/doc/update.sh @@ -0,0 +1,157 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$(dirname "$0")/../ulib/ulib" && +urequire DEFAULTS || +exit 1 +OENC="$UTF8" + +function display_help() { + local OENC="$UTF8" + uecho "$scriptname: Mettre à jour la documentation pour les outils de nutools + +USAGE + $scriptname [options] + +OPTIONS + -t Ne générer la documentation que pour les outils + -l Ne générer la documentation que pour ulib + -s Synchroniser avec nutools.html après la génération de la documentation +Par défaut, la documentation est (re)générée pour les outils et pour ulib" +} + +function dump_content() { + local twpage="$1" content="$2" + if [ -f "$twpage" ]; then + awk ' +BEGIN { dump = 0 } +!dump && ($0 == "" || $0 ~ /^[^#]/) { dump = 1 } +dump { print }' <"$twpage" >"$content" + else + >"$content" + fi +} + +function write_twpage() { + local oldcontent="$1" title="$2" newcontent="$3" twpage="$4" + if testdiff "$oldcontent" "$newcontent"; then + local created="$(date +"%d/%m/%Y %H:%M")" + echo "# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: $USER +##@created: $created +##@modifier: $USER +##@changecount: 1 +##@tags: +##@title: $title" >"$twpage" + cat "$newcontent" >>"$twpage" + fi +} + +auto=1 +tools= +ulib= +sync= +force= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -t '$auto=; tools=1' \ + -l '$auto=; ulib=1' \ + -s '$auto=; sync=1' \ + --force force=1 \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +[ -n "$auto" ] && { + tools=1 + ulib=1 + sync=1 +} +ac_set_tmpfile oldcontent +ac_set_tmpfile newcontent +cd "$scriptdir" + +if [ -n "$tools" ]; then + if [ -n "$*" ]; then + cmds=("$@") + else + array_lsfiles cmds .. + fi + + for cmd in "${cmds[@]}"; do + cmdname="$(basename "$cmd")" + twpage="$cmdname.twp" + [ -x "$cmd" ] || continue + estep "$cmdname" + + dump_content "$twpage" "$oldcontent" + echo " +{{{ +$("$cmd" --help) +}}}" >"$newcontent" + + write_twpage "$oldcontent" "$cmdname" "$newcontent" "$twpage" + done +fi + +if [ -n "$ulib" ]; then + array_from_lines ulibnames "$(list_files ../ulib)" + # faire l'entête + dump_content ulib.twp "$oldcontent" + echo " +!Liste des librairies de ulib" >"$newcontent" + for ulibname in "${ulibnames[@]}"; do + echo "* [[ulib/$ulibname]]" >>"$newcontent" + done + write_twpage "$oldcontent" ulib "$newcontent" ulib.twp + # faire les pages + for ulibname in "${ulibnames[@]}"; do + ulib="../ulib/$ulibname" + twpage="ulib_$ulibname.twp" + title="ulib/$ulibname" + dump_content "$twpage" "$oldcontent" + awkrun 'BEGIN { + in_func = 0 + dump_doc = 0 + dumped_doc = 0 + print +} +!in_func && $0 ~ /^function / { + if (match($0, /function +([^ ]+)\(\)/, vs)) { + funcname = vs[1] + if (funcname !~ /^_/) { + in_func = 1 + dump_doc = 1 + dumped_doc = 0 + print "!! {{{" funcname "}}}" + if ($0 ~ /}$/) { + in_func = 0 + dump_doc = 0 + dumped_doc = 0 + } + next + } + } +} +in_func && dump_doc && $0 !~ /^ *#/ { + dump_doc = 0 +} +in_func && dump_doc && $0 ~ /^ *#/ { + if (!dumped_doc) print "{{{" + gsub(/^ *#+ */, "") + print + dumped_doc = 1 +} +in_func && $0 ~ /}$/ { + if (dumped_doc) print "}}}" + in_func = 0 + dump_doc = 0 + dumped_doc = 0 +} +END { if (in_func) print "}}}" } +' <"$ulib" >"$newcontent" + write_twpage "$oldcontent" "$title" "$newcontent" "$twpage" + done +fi + +if [ -n "$sync" ]; then + ../twsync ${force:+--force} +fi diff --git a/doc/uprefix.twp b/doc/uprefix.twp new file mode 100644 index 0000000..494dd1a --- /dev/null +++ b/doc/uprefix.twp @@ -0,0 +1,21 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: uprefix + +{{{ +uprefix: Afficher les préfixes valides pour uinst + +USAGE + uprefix -l|--dump|prefix... + +OPTIONS + -l Afficher la liste des préfixes valides + --dump + Afficher la liste des préfixes valides et leurs valeurs + prefix + Afficher la valeur du préfixe spécifié +}}} diff --git a/doc/uproject.twp b/doc/uproject.twp new file mode 100644 index 0000000..9ac0e91 --- /dev/null +++ b/doc/uproject.twp @@ -0,0 +1,75 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: uproject + +{{{ +uproject: Outil pour gérer des projets + +USAGE + uproject cmd [args] + +COMMANDS + getvcs [dir] + Afficher le type de VCS pour dir. + getroot [dir] + Si dir est un répertoire versionné, retourner le répertoire racine du + projet versionné. + getrepos [dir] + Si dir est un répertoire versionné, retourner l'url du repository du + projet versionné. + geturl [dir] + Si dir est un répertoire versionné, retourner son url dans le + repository. + fold [dir] + unfold [dir] + Utiliser uinc pour défaire (resp. refaire) toutes les inclusions des + fichiers de dir. Cela ne fonctionne que si un fichier .uir est configuré + à la racine du projet avec inc=true (ou un fichier .uinst.conf avec + update_inc=true + vcs [args] + Appeler le gestionnaire de gestion approprié avec les arguments donnés. + add files... + Ajouter les fichiers files dans le gestionnaire de version. + remove files... + Supprimer les fichiers versionnés files. + copy from to + Copier le fichier versionné from vers le fichier to. + move from to + Renommer le fichier versionné from vers le fichier to. + mkdir dir + Créer un nouveau répertoire versionné. + commit message [files...] + Enregistrer les modifications (par défaut sur tous les fichiers + modifiés) avec le commentaire message. + status + Afficher l'état des fichiers versionnés et non versionnés. + update [-x] + Mettre à jour la copie locale avec la copie sur le serveur. + -x Ne pas mettre à jour les références externes (si appliquable) + diff [options] + Afficher les différences. + -l Afficher les différences non commitées (par défaut) + -c Afficher les différences en passe d'être commitées (si appliquable) + -r REV + Afficher les différences depuis la révision REV. + -R Afficher les modifications effectuées depuis la dernière release. + printml [-t TYPE] + Afficher le modeline pour un fichier du type spécifié + addml [-t TYPE] file + Ajouter un modele pour le fichier spécifié, s'il n'en a pas déjà un. + Si nécessaire, forcer le type du fichier au lieu de l'autodétecter + new [options] file [template options] + Créer un nouveau fichier à partir d'un modèle. + Avant le nom du fichier, les options suivantes sont valides: + -t TEMPLATE + Spécifier le modèle de fichier à utiliser. Par défaut, le modèle + à utiliser est déduit de l'extension ou du nom du fichier. + -e Editer le fichier après l'avoir créé. + Après le nom du fichier, toutes les options sont spécifiques au modèle + utilisé pour créer le nouveau fichier. Utiliser l'option --help pour + avoir une description des options disponibles. +}}} diff --git a/doc/usysinfos.twp b/doc/usysinfos.twp new file mode 100644 index 0000000..8cb9907 --- /dev/null +++ b/doc/usysinfos.twp @@ -0,0 +1,19 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: usysinfos + +{{{ +usysinfos: Afficher les informations sur le système + +USAGE + usysinfos [query] + +Si la requête est spécifiée, tester si le système courant correspond à la +requête. Voir la doc de check_sysinfos() pour le format de la requête. + +Sinon, afficher les informations sur le système courant. +}}} diff --git a/doc/vzusage.twp b/doc/vzusage.twp new file mode 100644 index 0000000..582a37a --- /dev/null +++ b/doc/vzusage.twp @@ -0,0 +1,25 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: vzusage + +{{{ +vzusage: afficher des informations sur une machine virtuelle OpenVZ + +USAGE + vzusage [options] [params...]d + +OPTIONS + -b Afficher les informations de /proc/user_beancounters + -f N'afficher que les valeurs pour lesquelles failcnt > 0. + Implique -b + -z coef + Afficher les instructions à utiliser pour augmenter de coef% les + valeurs pour lesquelles failcnt > 0. Implique -f + -c config|veid + Afficher les informations du fichier de configuration plutôt que les + beancounters +}}} diff --git a/doc/woArchive.twp b/doc/woArchive.twp new file mode 100644 index 0000000..6a80b50 --- /dev/null +++ b/doc/woArchive.twp @@ -0,0 +1,21 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: woArchive + +{{{ +woArchive: créer une archive de la distribution WebObjects en cours +USAGE + woArchive [-n name] [-f files] + +OPTIONS + -n NAME + Nom de base de l'archive. Par défaut il s'agit de WebObjects- + + -f FILES + Nom de la liste des fichiers de l'archives. Par défaut il s'agit de + $NAME.files +}}} diff --git a/doc/woSwitch.twp b/doc/woSwitch.twp new file mode 100644 index 0000000..03f8665 --- /dev/null +++ b/doc/woSwitch.twp @@ -0,0 +1,33 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: woSwitch + +{{{ +woSwitch: Changer la version de WebObjects pour le système en cours +USAGE + woSwitch [-f from.files] to-archive.tar.gz + +OPTIONS + -f from.files + Spécifier la liste des fichiers pour la version de WebObjects + installée. Par défaut, il s'agit de WebObjects-.files + La liste doit correspondre à la version en cours. + + -F Forcer l'installation, même si la version en cours ne correspond pas à ce + qui est inscrit dans from.files + + to-archive.tar.gz + Nom de l'archive qui contient la version à installer. + +Ce script ne fonctionne que sur MacOS X +Le contenu des répertoires suivants est sauvegardé avant le changement: + /Library/WebObject/Applications + /Library/WebObject/Configuration + /Library/WebObject/Extensions +Ensuite, les répertoires Applications et Configuration sont restaurés. Il faudra +restaurer Extensions manuellement. +}}} diff --git a/doc/woctl.twp b/doc/woctl.twp new file mode 100644 index 0000000..b599f74 --- /dev/null +++ b/doc/woctl.twp @@ -0,0 +1,58 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: woctl + +{{{ +woctl: Contrôler des applications WebObjects + +USAGE + woctl [options] action args + wostart args... + wostop args... + wobounce args... + worestart args... + +OPTIONS + -h HOST + Spécifier l'hôte qui fait tourner le moniteur sous la forme host[:port] + -p PASSWORD + Spécifier le mot de passe pour le moniteur + +ACTIONS + Dans les arguments des actions ci-dessous, une application peut être + spécifiée sous la forme App ou App.woa. Spécifier une application revient à + spécifier toutes les instances configurées pour cette application. + Si on spécifie un framework sous la forme Fwk.framework, cela revient à + spécifier toutes les applications qui dépendent de ce framework. + Sinon, une instance individuelle est de la forme App-N, où N est un entier + positif. + + status + afficher l'état des instances qui tournent actuellement + version + afficher la version de WebObjects installée + wotaskd + javamonitor + woservices + piloter wotaskd/javamonitor + configure + configurer un bundle + tag + ajouter une information de version à un bundle + run + lancer une application localement en mode debug + download + télécharger une application ou un framework + start apps... + démarrer une ou plusieurs applications + stop apps... + arrêter une ou plusieurs applications + restart apps... + relancer une ou plusieurs applications + bounce apps... + relancer une ou plusieurs applications en mode bounce +}}} diff --git a/doc/woinst.twp b/doc/woinst.twp new file mode 100644 index 0000000..0692c0a --- /dev/null +++ b/doc/woinst.twp @@ -0,0 +1,29 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: woinst + +{{{ +woinst: Déployer un bundle (application ou framework) de WebObjects + +USAGE + woinst [options] ... + +OPTIONS + PREFIX=value + Spécifier une valeur pour un préfixe, plutôt que de laisser uprefix + l'autodétecter. Utiliser uprefix -l pour une liste de préfixes valides. + -b Redémarrer les instances en mode bounce. + Par défaut, les instances sont arrêtées avant le déploiement, et + redémarrées après + -W Ne déployer que les resources web. Implique -n + -n Ne pas tagger les bundles déployés avec un numéro de version. Par + défaut, l'utilisateur est invité à compléter des informations telles + que n° de version et date de release si ces informations ne sont pas + disponible. + -x CMD + Exécuter la commande CMD après avoir effectué le déploiement +}}} diff --git a/doc/wosign.twp b/doc/wosign.twp new file mode 100644 index 0000000..cebfb75 --- /dev/null +++ b/doc/wosign.twp @@ -0,0 +1,21 @@ +# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@creator: jclain +##@created: 15/03/2012 22:20 +##@modifier: jclain +##@changecount: 1 +##@tags: +##@title: wosign + +{{{ +wosign: signer les jars d'un bundle + +USAGE + wosign + +OPTIONS + -f Forcer la (re)signature des jars + -d Enlever la signature des jars originaux + -s Signer les jar du bundle [PAR DEFAUT] + --init + Initialiser les fichiers de configuration pour la signature des bundles. +}}} diff --git a/dokuwiki b/dokuwiki new file mode 100755 index 0000000..94ef103 --- /dev/null +++ b/dokuwiki @@ -0,0 +1,806 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Outils pour gérer une installation de DokuWiki + +USAGE + $scriptname cmd [options] + +COMMANDES + newpage titre + Créer une nouvelle page avec le titre spécifié + newlog [titre] + Créer une nouvelle page datée du jour. Equivalent à newpage --log. + find filtre + Chercher les pages dont le nom correspondent au filtre + edit filtre + Editer la première page qui correspond au filtre + commit [msg] + Enregistrer les modifications dans le gestionnaire de version. + sync + Synchroniser un dokuwiki local vers une installation système. Requière + les droits de root. + generate srcdir + Générer la documentation du projet srcdir dans un espace de nom" +} + +SCRIPT_ALIASES=( + dwa:newpage + dwl:find + dwe:edit + dwci:commit + dwsync:sync +) + +if [ "$#" -eq 1 -a "$1" == --nutools-makelinks ]; then + # créer les liens + scriptname="$(basename "$0")" + for alias in "${SCRIPT_ALIASES[@]}"; do + alias="${alias%:*}" + ln -s "$scriptname" "$alias" + done + exit 0 +fi + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +# Traduire le nom du script +for script_alias in "${SCRIPT_ALIASES[@]}"; do + splitpair "$script_alias" src dest + if [ "$scriptname" == "$src" ]; then + eval "set -- $dest \"\$@\"" + scriptname=dokuwiki + break + fi +done + +DWCOMMIT=1 +set_defaults dokuwiki + +function __check_dwdir0() { + [ -f "$1/doku.php" -a -d "$1/data/pages" -a -d "$1/data/media" ] +} +function __check_dwdir1() { + [ -f "$1/.dokuwiki" ] +} +function __check_dwdir() { + __check_dwdir0 "$1" && return 0 + __check_dwdir1 "$1" && return 0 + return 1 +} +function find_dwdir() { + # trouver le répertoire du dokuwiki correspondant au répertoire $1 et + # retourner 0. Retourner 1 si $1 n'est pas dans un répertoire de dokuwiki. + local dwdir="$(abspath "${1:-.}")" + while [ "$dwdir" != "/" ]; do + if __check_dwdir "$dwdir"; then + echo "$dwdir" + return 0 + fi + dwdir="$(dirname "$dwdir")" + done + if [ -n "$DOKUWIKIDIR" ] && __check_dwdir "$DOKUWIKIDIR"; then + echo "$(abspath "$DOKUWIKIDIR")" + return 0 + fi + return 1 +} + +function get_pagesdir() { + # Obtenir le répertoire des pages du dokuwiki $1, qui a déjà été normalisé + # avec find_dwdir() + if [ -f "$1/doku.php" ]; then + echo "$1/data/pages" + elif [ -f "$1/.dokuwiki" ]; then + echo "$1" + fi +} +function get_mediadir() { + # Obtenir le répertoire de media du dokuwiki $1, qui a déjà été normalisé + # avec find_dwdir() + if [ -f "$1/doku.php" ]; then + echo "$1/data/media" + elif [ -f "$1/.dokuwiki" ]; then + echo "$1/_media" + fi +} + +function in_datadir() { + # retourner 0 si le répertoire ou le fichier $1 est dans le répertoire pages + # ou media d'un dokuwiki. + local data dwdir + data="$(abspath "${1:-.}")" + dwdir="$(find_dwdir "$data")" || return 1 + local pagesdir="$(get_pagesdir "$dwdir")" + [ "$data" == "pagesdir" -o "${data#$pagesdir/}" != "$data" ] && return 0 + local mediadir="$(get_mediadir "$dwdir")" + [ "$data" == "mediadir" -o "${data#$mediadir/}" != "$data" ] && return 0 + return 1 +} + +function compute_ns() { + # calculer le namespace correspondant au fichier ou au répertoire $1 et + # retourner 0. Le namespace est retourné normalisé, c'est à dire "" si pas + # de namespace, et "ns:" pour le namespace ns. + # retourner 1 si le namespace n'a pas pu être calculé, notamment parce que + # $1 n'est pas un fichier du dokuwiki. + local datadir dwdir ns + datadir="$(abspath "${1:-.}")" + if [ -e "$datadir" ]; then + [ -d "$datadir" ] || datadir="$(dirname "$datadir")" + fi + in_datadir "$datadir" || return 1 + + dwdir="$(find_dwdir "$datadir")" || return 1 + + local mediadir="$(get_mediadir "$dwdir")" + if [ "$datadir" == "mediadir" ]; then + return 0 + elif [ "${datadir#$mediadir/}" != "$datadir" ]; then + ns="${datadir#$mediadir/}" + echo "${ns//\//:}" + return 0 + fi + + local pagesdir="$(get_pagesdir "$dwdir")" + if [ "$datadir" == "pagesdir" ]; then + return 0 + elif [ "${datadir#$pagesdir/}" != "$datadir" ]; then + ns="${datadir#$pagesdir/}" + echo "${ns//\//:}" + return 0 + fi + + return 1 +} + +function norm_ns() { + # normaliser un namespace: le faire terminer par ":" s'il est non vide. + local ns="$1" + if [ -n "$ns" ]; then + [ "${ns%:}" == "$ns" ] && ns="$ns:" + echo "$ns" + fi +} +function clean_ns() { + # supprimer le ":" de fin d'un namespace + [ -n "$1" ] && echo "${1%:}" +} + +function __create() { + local dwdir="$1" file="$2" title="$3" __newfiles="$4" + + estepi "Création de la page $(ppath "$file")" + echo "# -*- coding: utf-8 mode: text -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +===== $title ===== + +" >"$file" + [ -n "$__newfiles" ] && array_add "$__newfiles" "$file" + [ -n "$edit" ] && "${EDITOR:-vi}" +5 "$file" + return 0 +} + +function __addtostart() { + local dwdir="$1" ns="$2" name="$3" title="$4" __newfiles="$5" __modfiles="$6" + + local basestart="$(get_pagesdir "$dwdir")/${ns//://}start" + local start="$basestart.txt" + mkdirof "$start" + + if [ -f "$start" ]; then + [ -n "$__modfiles" ] && array_add "$__modfiles" "$start" + else + echo >"$start" "\ +# -*- coding: utf-8 mode: text -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +" + [ -n "$__newfiles" ] && array_add "$__newfiles" "$start" + fi + + quietgrep "=== Pages non classées ===" "$start" || echo >>"$start" "\ +===== Pages non classées ===== +" + echo " * [[$ns$name|$title]]" >>"$start" +} + +function __strip_dwdir() { + if [ "$1" == "$dwdir" ]; then + echo . + else + echo "${1#$dwdir/}" + fi +} +function __commit() { + local dwdir="$1" msg="$2" + [ -d "$dwdir/.git" ] || return 0 + + local -a __newfiles __modfiles + [ -n "$3" ] && array_copy __newfiles "$3" + array_map __newfiles __strip_dwdir + [ -n "$4" ] && array_copy __modfiles "$4" + array_map __modfiles __strip_dwdir + + cwd="$(pwd)" + cd "$dwdir" + if [ -n "${__newfiles[*]}" -o -n "${__modfiles[*]}" ]; then + "$scriptdir/uproject" add "${__newfiles[@]}" "${__modfiles[@]}" + else + "$scriptdir/uproject" add . + fi + "$scriptdir/uproject" commit "$msg" "${__newfiles[@]}" "${__modfiles[@]}" + cd "$cwd" +} + +################################################################################ +function newpage_help() { + uecho "$scriptname newpage: créer une nouvelle page de wiki + +USAGE + $scriptname newpage PAGE + +PAGE est de la forme [ns:]titre +- ns est l'espace de nom dans lequel la nouvelle page doit être créée. +- titre est le titre de la nouvelle page. +- nom est le nom du fichier sur disque. il est calculé à partir de titre. +Toutes ces valeurs peuvent être forcées individuellement. + +OPTIONS + -d DWDIR + Spécifier le répertoire du dokuwiki + -t TITRE + Forcer la valeur du titre de la page au lieu de la calculer à partir de + PAGE. + -s NS + Spécifier l'espace de nom dans lequel créer la nouvelle page au lieu de + le calculer à partir de PAGE ou du répertoire courant. + -n NOM + Forcer la valeur du nom de fichier à utiliser au lieu de la calculer à + partir de PAGE. + --log + Dater la nouvelle page de la date du jour. Cette option est utile pour + documenter des opérations journalières. + -c + Ne pas lancer l'édition du fichier après l'avoir créé. Si DWCOMMIT=1, ne + pas lancer l'enregistrement des modifications dans le gestionnaire de + version. + -o + Ne pas rajouter la mention de la nouvelle page dans [[start]]" +} +function newpage_cmd() { + eval "$(utools_local)" + local dwdir title ns name log + local edit=1 addtostart=1 + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with newpage_help' \ + -d:,--dwdir: dwdir= \ + -t:,--title: title= \ + -s:,--ns:,--namespace: ns= \ + -n:,--name: name= \ + -l,--log log=1 \ + -c,--noedit edit= \ + -o,--orphan addtostart= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + dwdir="$(find_dwdir "$dwdir")" || die "Impossible de trouver dokuwiki. Etes-vous dans le bon répertoire?" + + if [ -z "$ns" ]; then + ns="$(compute_ns .)" + fi + if [ -z "$title" ]; then + title="$1" + if [ -n "$title" ]; then + # éventuellement prendre le namespace dans le titre fourni en ligne + # de commande + if [ -z "$ns" ]; then + ns="$(echo "$title" | awk '{ + ns = tolower($0) + if (match(ns, /^([-a-z0-9]+:)+/)) { + print substr(ns, 1, RLENGTH - 1) + } +}')" + [ -n "$ns" ] && title="${title:$((${#ns} + 1))}" + fi + elif [ -z "$log" ]; then + read_value "Veuillez entrer un titre pour la page de wiki à créer" title + fi + fi + ns="$(norm_ns "$ns")" + + if [ -z "$name" ]; then + name="$(echo "$title" | awk '{ + name = tolower($0) + gsub(/[^- /a-z0-9]/, "", name) + gsub(/\//, "-", name) + gsub(/ +/, "-", name) + print name +}')" + fi + if [ -n "$log" ]; then + title="$(date +"%d/%m/%Y")${title:+: $title}" + name="$(date +"%Y-%m-%d")${name:+-$name}" + fi + read_value "Veuillez confirmer le nom de la page" name "$name" + + local basename="$name" + local basefile="$(get_pagesdir "$dwdir")/${ns//://}$name" + local file="$basefile.txt" + + if [ -f "$file" ]; then + estepw "Le fichier $(ppath "$file") existe déjà." + ask_yesno "Si vous continuez, un NOUVEAU fichier avec un suffixe numérique sera créé. +Sinon, vous pouvez utiliser '$scriptname edit' pour modifier le fichier existant. +Voulez-vous continuer?" O || return 1 + fi + local i=0 + while [ -f "$file" ]; do + i=$(($i + 1)) + name="$basename-$i" + file="$basefile-$i.txt" + done + mkdirof "$file" + + local -a newfiles modfiles + __create "$dwdir" "$file" "$title" newfiles || return + if [ -n "$addtostart" ]; then + __addtostart "$dwdir" "$ns" "$name" "$title" newfiles modfiles || return + fi + if [ -n "$edit" -a -n "$DWCOMMIT" ]; then + __commit "$dwdir" "newpage $title --> $ns$name" newfiles modfiles || return + else + estepi "dwci $(quoted_args "dwci newpage $title --> $ns$name")" + fi + return 0 +} + +################################################################################ +function find_help() { + uecho "$scriptname find: trouver une page + +USAGE + $scriptname find + +OPTIONS + -d DWDIR + Spécifier le répertoire de dokuwiki + -s NAMESPACE + Restreindre la recherche à l'espace de nom spécifié" +} +function find_cmd() { + eval "$(utools_local)" + local dwdir ns + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with find_help' \ + -d:,--dwdir: dwdir= \ + -s:,--ns:,--namespace: ns= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + dwdir="$(find_dwdir "$dwdir")" || die "Impossible de trouver dokuwiki. Etes-vous dans le bon répertoire?" + ns="$(clean_ns "$ns")" + + local filter="$1" + + local pages="$(get_pagesdir "$dwdir")" + local searchdir="$pages" + [ -n "$ns" ] && searchdir="$searchdir/${ns//://}" + + [ -d "$searchdir" ] || return 1 + find "$searchdir" -type f -name "*.txt" | + sed "s#^$pages/##g; s#.txt\$##g" | + csort | { + if [ -n "$filter" ]; then + grep "$filter" + else + cat + fi + } +} + +################################################################################ +function edit_help() { + uecho "$scriptname edit: modifier une page + +USAGE + $scriptname edit + +OPTIONS + -d DWDIR + Spécifier le répertoire de dokuwiki + -s NAMESPACE + Restreindre la recherche à l'espace de nom spécifié" +} +function edit_cmd() { + eval "$(utools_local)" + local dwdir ns + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with edit_help' \ + -d:,--dwdir: dwdir= \ + -s:,--ns:,--namespace: ns= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + dwdir="$(find_dwdir "$dwdir")" || die "Impossible de trouver dokuwiki. Etes-vous dans le bon répertoire?" + ns="$(clean_ns "$ns")" + + local pagesdir="$(get_pagesdir "$dwdir")" + local filter="$1" found= + if [ -f "$filter" ]; then + local file="$(abspath "$filter")" + if [ "${file#$pagesdir/}" != "$file" -a "${file%.txt}" != "$file" ]; then + page="${file#$pagesdir/}" + page="${page%.txt}" + found=1 + fi + fi + if [ -z "$found" ]; then + local -a pages + array_from_lines pages "$(find_cmd -qq -d "$dwdir" -n "$ns" "$filter")" + if [ "${#pages[*]}" -eq 0 ]; then + eerror "Aucune page de ce nom n'a été trouvée" + return 1 + elif [ "${#pages[*]}" -eq 1 ]; then + page="${pages[0]}" + else + simple_menu page pages -t "Pages trouvées" \ + -m "Veuillez choisir la page à éditer" -d "${pages[0]}" + fi + fi + + local -a newfiles modfiles + "${EDITOR:-vi}" "$pagesdir/$page.txt" + array_add modfiles "$pagesdir/$page.txt" + + if [ -n "$DWCOMMIT" ]; then + __commit "$dwdir" "edit ${page//\//:}" newfiles modfiles || return + else + estepi "dwci $(quoted_args "edit ${page//\//:}")" + fi + return 0 +} + +################################################################################ +function commit_help() { + uecho "$scriptname commit: enregistrer les modifications dans le gestionnaire de version + +USAGE + $scriptname commit + +OPTIONS + -d DWDIR + Spécifier le répertoire de dokuwiki" +} +function commit_cmd() { + eval "$(utools_local)" + local dwdir + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with commit_help' \ + -d:,--dwdir: dwdir= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + dwdir="$(find_dwdir "$dwdir")" || die "Impossible de trouver dokuwiki. Etes-vous dans le bon répertoire?" + + __commit "$dwdir" "$*" || return + return 0 +} + +################################################################################ +function sync_help() { + uecho "$scriptname sync: synchroniser les fichiers vers un dokuwiki système. + +USAGE + $scriptname sync [destdir] + +OPTIONS + -d DWDIR + Spécifier le répertoire du dokuwiki local + --destdir DESTDIR + Spécifier le répertoire du dokuwiki système. + DESTDIR peut être un répertoire distant puisque la copie se fait avec + rsync. Mais les paramètres --dirmode, --filemode et --owner sont alors + ignorés. De plus, les indexes ne sont pas reconstruits. + Il est aussi possible de spécifier DESTDIR comme argument de ce script. + --dirmode DIRMODE + --filemode FILEMODE + --owner OWNER + Spécifier les modes et propriétaires des fichiers dans le dokuwiki + destination. + --no-reindex + Ne pas reconstruire l'index ni supprimer le cache après la mise à jour + des fichiers." +} +function sync_cmd() { + eval "$(utools_local)" + local dwdir destdir noconf + local dirmode filemode owner noreindex + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with sync_help' \ + --noconf noconf=1 \ + -d:,--dwdir: dwdir= \ + --destdir: destdir= \ + --dirmode: dirmode= \ + --filemode: filemode= \ + --owner: owner= \ + -n,--no-reindex noreindex=1 \ + --reindex noreindex= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + if [ -z "$noconf" ]; then + dwdir="$(find_dwdir "$dwdir")" || die "Impossible de trouver dokuwiki. Etes-vous dans le bon répertoire?" + + [ -f "$dwdir/.dokuwiki" ] && source "$dwdir/.dokuwiki" + [ -z "$destdir" -a -n "$1" ] && destdir="$1" + [ -n "$destdir" ] || destdir="$DWSYNC_DESTDIR" + [ -n "$destdir" ] || die "Vous devez spécifier le répertoire de destination" + + [ -n "$dirmode" ] || dirmode="$DWSYNC_DIRMODE" + [ -n "$filemode" ] || filemode="$DWSYNC_FILEMODE" + [ -n "$owner" ] || owner="$DWSYNC_OWNER" + + [ -n "$url" ] || url="$DWSYNC_URL" + fi + + run_as_root sync $(get_verbosity_option) --noconf -d "$dwdir" --destdir "$destdir" \ + ${dirmode:+--dirmode "$dirmode"} \ + ${filemode:+--filemode "$filemode"} \ + ${owner:+--owner "$owner"} \ + ${noreindex:+--no-reindex} "$@" + + local -a rsync reindexer + rsync=(rsync -rltD --delete-after) + reindexer=("$destdir/bin/indexer.php") + if check_verbosity -v; then + array_add rsync -v + elif ! check_verbosity -c; then + array_add rsync -q + array_add reindexer -q + fi + + local srcmdir="$(get_mediadir "$dwdir")" + local srcpdir="$(get_pagesdir "$dwdir")" + local destmdir="$destdir/data/media" + local destpdir="$destdir/data/pages" + etitle "Synchronisation des fichiers" + if __check_dwdir0 "$dwdir"; then + # pagesdir et mediadir séparés + estep "$(ppath "$srcmdir") --> $(ppath "$destmdir")" + "${rsync[@]}" "$srcmdir/" "$destmdir" + estep "$(ppath "$srcpdir") --> $(ppath "$destpdir")" + "${rsync[@]}" "$srcpdir/" "$destpdir" + elif __check_dwdir1 "$dwdir"; then + # mediadir dans pagesdir + estep "$(ppath "$srcmdir") --> $(ppath "$destmdir")" + "${rsync[@]}" "$srcmdir/" "$destmdir" + estep "$(ppath "$srcpdir") --> $(ppath "$destpdir")" + "${rsync[@]}" \ + --exclude /.git --exclude /.dokuwiki --exclude /.udir \ + --exclude /_media \ + --exclude .svn \ + "$srcpdir/" "$destpdir" + fi + eend + + if [ -n "$dirmode" ]; then + etitle "dirmode=$dirmode" + estep "$(ppath "$destmdir")" + find "$destmdir" -type d -exec chmod "$dirmode" {} \; + estep "$(ppath "$destpdir")" + find "$destpdir" -type d -exec chmod "$dirmode" {} \; + eend + fi + if [ -n "$filemode" ]; then + etitle "filemode=$filemode" + estep "$(ppath "$destmdir")" + find "$destmdir" -type f -exec chmod "$filemode" {} \; + estep "$(ppath "$destpdir")" + find "$destpdir" -type f -exec chmod "$filemode" {} \; + eend + fi + if [ -n "$owner" ]; then + etitle "owner=$owner" + estep "$(ppath "$destmdir")" + chown -R "$owner" "$destmdir" + estep "$(ppath "$destpdir")" + chown -R "$owner" "$destpdir" + eend + fi + + if [ -z "$noreindex" -a -x "${reindexer[0]}" ]; then + etitle "Réindexation des fichiers" + "${reindexer[@]}" + + #estep "Suppression du cache" + #local -a cachedirs + #array_lsdirs cachedirs "$destdir/data/cache" + #rm -rf "${cachedirs[@]}" + estep "Invalidation du cache" + touch "$destdir/conf/local.php" + + if [ -n "$owner" ]; then + estep "owner=$owner" + chown -R "$owner" "$destdir/data/index" + fi + eend + fi +} + +################################################################################ +function generate_help() { + uecho "$scriptname generate: Générer la documentation d'un projet dans un espace de nom + +USAGE + $scriptname generate [options] srcdir + +Si srcdir contient un fichier .dokuwikigen, ce fichier est sourcé pour générer +la documentation. Les fonction setpage() et addpage() sont disponible. + +OPTIONS + -d DWDIR + Spécifier le répertoire du dokuwiki + -s NS + Spécifier l'espace de nom dans lequel créer la documentation. + Par défaut, il s'agit de da:srcname où srcname est le nom de base + de srcdir." +} +function generate_cmd() { + eval "$(utools_local)" + local dwdir ns title + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with generate_help' \ + -d:,--dwdir: dwdir= \ + -s:,--ns:,--namespace: ns= \ + -t:,--title: title= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + dwdir="$(find_dwdir "$dwdir")" || die "Impossible de trouver dokuwiki. Etes-vous dans le bon répertoire?" + + local srcdir="$(abspath "${1:-.}")" + [ -d "$srcdir" ] || die "Vous devez spécifier un répertoire de projet dont il faut générer la documentation" + local srcname=$(basename "$srcdir") + + [ -n "$ns" ] || ns="da:$srcname" + ns="$(norm_ns "$ns")" + + [ -n "$title" ] || title="$srcname" + + function setpage() { + # Créer la page $1 avec le contenu de l'entrée standard + # $2 est un namespace local en dessous de $ns + local append + if [ "$1" == "--append" ]; then + append=1 + shift + fi + local name="$1" localns="$2" + + name="$(awk '{ +gsub(/_/, "") +print tolower($0) +}' <<<"$name")" + # XXX normaliser le nom: en plus de le mettre en minuscule, il faut + # supprimer certains caractères spéciaux comme '_'. en faire la liste? + + ns="$(norm_ns "$ns")" + [ -n "$localns" ] && local ns="$(norm_ns "$ns$localns")" + local basefile="$(get_pagesdir "$dwdir")/${ns//://}$name" + local file="$basefile.txt" + mkdirof "$file" + + if [ -f "$file" ]; then + local -a __newfiles + array_from_lines __newfiles "$(<"$newfiles")" + array_contains __newfiles "$file" || echo "$file" >>"$modfiles" + else + echo "$file" >>"$newfiles" + fi + if [ -n "$append" ]; then + cat >>"$file" + else + estepi "$(ppath "$file")" + cat >"$file" + fi + } + function addpage() { + # ajouter le contenu de l'entrée standard à la page $1 + setpage --append "$@" + } + function setmedia() { + # Créer le fichier de media $2 en copiant le contenu du fichier $1 + # $3 est un namespace local en dessous de $ns + local source="$1" name="$2" localns="$3" + + name="$(awk '{ +gsub(/_/, "") +print tolower($0) +}' <<<"$name")" + # XXX normaliser le nom: en plus de le mettre en minuscule, il faut + # supprimer certains caractères spéciaux comme '_'. en faire la liste? + + ns="$(norm_ns "$ns")" + [ -n "$localns" ] && local ns="$(norm_ns "$ns$localns")" + local file="$(get_mediadir "$dwdir")/${ns//://}$name" + mkdirof "$file" + + if [ -f "$file" ]; then + local -a __newfiles + array_from_lines __newfiles "$(<"$newfiles")" + array_contains __newfiles "$file" || echo "$file" >>"$modfiles" + else + echo "$file" >>"$newfiles" + fi + estepi "$(ppath "$file")" + cat "$source" >"$file" + } + function gendefault() { + setpage start <<<"===== $title ===== +" + if [ -f README.txt ]; then + { + awk 'NR==1 && $0 ~ /^#.*-\*-.*coding:/ {next} {print}' " + "$cmd" --help + echo "" + } | setpage "$cmdname" + if [ -n "$first" ]; then + addpage start <<<"==== Outils ====" + first= + fi + addpage start <<<" * [[$ns$cmdname]]" + done + eend + } + + local newfiles modfiles + ac_set_tmpfile newfiles + ac_set_tmpfile modfiles + ( + cd "$srcdir" + if [ -x .dokuwikigen ]; then + ./.dokuwikigen + elif [ -f .dokuwikigen ]; then + source ./.dokuwikigen + else + gendefault + fi + array_from_lines newfiles "$(<"$newfiles")" + array_from_lines modfiles "$(<"$modfiles")" + if [ -n "$DWCOMMIT" ]; then + __commit "$dwdir" "generate $srcdir" newfiles modfiles + else + estepi "dwci $(quoted_args "generate $srcdir")" + fi + ) +} + +################################################################################ + +parse_opts + "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +cmd="$1"; shift +case "$cmd" in +"") exit_with display_help;; +newpage|createpage|addpage|page|p|new|n|create|c|add|a) newpage_cmd "$@";; +newlog|createlog|log|blog|date|d) newpage_cmd --log "$@";; +find|f|search|s|list|l) find_cmd "$@";; +edit|e|vim|vi) edit_cmd "$@";; +commit|ci) commit_cmd "$@";; +sync) sync_cmd "$@";; +generate|gen|g) generate_cmd "$@";; +*) die "$cmd: commande incorrecte";; +esac diff --git a/fconv b/fconv new file mode 100755 index 0000000..afede28 --- /dev/null +++ b/fconv @@ -0,0 +1,134 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: convertir des fichiers dans un autre encoding + +USAGE + $scriptname [-f src_enc] [ -t dest_enc] [/path/to/file] + +OPTIONS + -f from + Encoding source. Si n'est pas spécifié ou vaut 'detect', l'encoding est + autodétecté. + -t to + Encoding destination. Doit être spécifié. + Cas particulier: si to vaut 'lf' ou 'crlf', from est ignoré, et seuls + les caractères de fin de lignes sont convertis. + -N Ne pas optimiser le calcul de l'encoding. Cette option n'est valide que + si -f n'est pas spécifié. On assume que tous les noms de fichiers n'ont + pas le même encoding. L'encoding from est donc recalculé à chaque fois. + -r inverser from et to, qui doivent être tous les deux spécifiés." +} + +if [ "$#" -eq 1 -a "$1" == --nutools-makelinks ]; then + # créer les liens + scriptname="$(basename "$0")" + for destenc in latin1 utf8 lf crlf cr; do + ln -s "$scriptname" "${scriptname}2$destenc" + done + exit 0 +fi + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +from=detect +case "${scriptname#fconv2}" in +utf8) to=utf-8;; +latin1) to=iso-8859-1;; +lf) to=lf;; +crlf) to=crlf;; +cr) to=cr;; +*) to=;; +esac +optimize=1 +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -f: from= \ + -t: to= \ + -N optimize= \ + -r reverse \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +function dfconv() { + # $1 = répertoire dont les fichiers doivent être convertis + local srcdir="$1" files file + array_lsall files "$srcdir" + for file in "${files[@]}"; do + fconv "$file" + done +} + +function ffconv() { + # $1 = fichier à renommer + local lfrom="$from" lto="$to" + local src="$1" + + if [ "$to" == "lf" ]; then + ebegin "$src" nl2lf "$src" + elif [ "$to" == "crlf" ]; then + ebegin "$src" nl2crlf "$src" + elif [ "$to" == "cr" ]; then + ebegin "$src" nl2cr "$src" + else + if [ "$lfrom" == "detect" ]; then + lfrom="$("$scriptdir/lib/pywrapper" uencdetect.py -f "$src")" + if [ "$lfrom" == "Unknown" ]; then + eerror "$(ppath "$src"): Impossible de déterminer l'encoding" + return 1 + fi + if [ -n "$optimize" ]; then + from="$(__norm_encoding "$lfrom")" + [ "$from" != "$to" ] && enote "Conversion $from --> $to" + fi + fi + if [ -n "$optimize" -a "$from" == "$to" ]; then + die "Une conversion de $from à $to n'a pas de sens. +Veuillez re-essayer avec -N si nécessaire." + fi + + if [ -n "$reverse" ]; then + local tmp="$lto" + lto="$lfrom" + lfrom="$tmp" + fi + + local tmp="$src.tmp.$$" + autoclean "$tmp" + ebegin "$src" + iconv -f "$lfrom" -t "$lto" "$src" >"$tmp" && + (cat "$tmp" >"$src"; edot) && + (rm "$tmp"; edot) + eend + fi +} + +function fconv() { + if [ -d "$1" ]; then + etitle "$(ppath "$1")" dfconv "$1" + elif [ -f "$1" ]; then + ffconv "$1" + else + eerror "$1: fichier introuvable ou invalide" + fi +} + +[ -n "$to" ] || die "Il faut spécifier l'encoding de destination" +to="$(__norm_encoding "$to")" + +if [ -n "$*" ]; then + if [ "$to" == "lf" -o "$to" == "crlf" -o "$to" == "cr" ]; then + enote "Conversion [cr]lf --> $to" + fi + for src in "$@"; do + fconv "$src" + done +elif [ "$to" == "lf" ]; then + nl2lf +elif [ "$to" == "crlf" ]; then + nl2crlf +elif [ "$to" == "cr" ]; then + nl2cr +fi diff --git a/fnconv b/fnconv new file mode 100755 index 0000000..ce0a593 --- /dev/null +++ b/fnconv @@ -0,0 +1,101 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: renommer des fichiers en changeant leur encoding + +USAGE + $scriptname [-f src_enc] [ -t dest_enc] [/path/to/file] + +OPTIONS + -f from + Encoding source. Si n'est pas spécifié ou vaut 'detect', l'encoding est + autodétecté. + -t to + Encoding destination. Doit être spécifié. + -N Ne pas optimiser le calcul de l'encoding. Cette option n'est valide que + si -f n'est pas spécifié. On assume que tous les noms de fichiers n'ont + pas le même encoding. L'encoding from est donc recalculé à chaque fois. + -r inverser from et to, qui doivent être tous les deux spécifiés." +} + +if [ "$#" -eq 1 -a "$1" == --nutools-makelinks ]; then + # créer les liens + scriptname="$(basename "$0")" + for destenc in latin1 utf8; do + ln -s "$scriptname" "${scriptname}2$destenc" + done + exit 0 +fi + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +from=detect +case "${scriptname#fnconv2}" in +utf8) to=utf-8;; +latin1) to=iso-8859-1;; +*) to=;; +esac +optimize=1 +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -f: from= \ + -t: to= \ + -N optimize= \ + -r reverse \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +function fnconv() { + # $1 = fichier à renommer + local lfrom="$from" lto="$to" + local srcdir="$(dirname "$1")" + local srcname="$(basename "$1")" + if [ "$lfrom" == "detect" ]; then + lfrom="$("$scriptdir/lib/pywrapper" uencdetect.py "$srcname")" + if [ "$lfrom" == "Unknown" ]; then + eerror "$srcname: Impossible de déterminer l'encoding" + return 1 + fi + if [ -n "$optimize" ]; then + from="$(__norm_encoding "$lfrom")" + [ "$from" != "$to" ] && enote "Conversion $from --> $to" + fi + fi + if [ -n "$optimize" -a "$from" == "$to" ]; then + die "Une conversion de $from à $to n'a pas de sens. +Veuillez re-essayer avec -N si nécessaire." + fi + + if [ -n "$reverse" ]; then + tmp="$lto" + lto="$lfrom" + lfrom="$tmp" + fi + + local src="$(abspath "$srcdir/$srcname")" + local dest + dest="$srcdir/$(iconv -f "$lfrom" -t "$lto" <<<"$srcname")" || return 1 + dest="$(abspath "$dest")" + if [ "$src" != "$dest" ]; then + ebegin "$(ppath "$src") --> $(basename "$dest")" \ + mv "$src" "$dest" + fi + src="$dest" + if [ -d "$src" ]; then + etitle -s "$(ppath "$src")" + local subsrcs subsrc + array_lsall subsrcs "$src" + for subsrc in "${subsrcs[@]}"; do + fnconv "$subsrc" + done + eend + fi +} + +[ -n "$to" ] || die "Il faut spécifier l'encoding de destination" +to="$(__norm_encoding "$to")" +for src in "$@"; do + fnconv "$src" +done diff --git a/geturl b/geturl new file mode 100755 index 0000000..ddb3c25 --- /dev/null +++ b/geturl @@ -0,0 +1,37 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Télécharger un fichier avec wget ou curl + +USAGE + $scriptname [wget options]" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +parse_opts + "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +url="$("$scriptdir/caturl" "$1")" || die +shift + +filename="$(basename "$url")" +if progexists wget; then + if [ -f "$filename" ]; then + wget -c "$@" "$url" + else + wget "$@" "$url" + fi +elif progexists curl; then + if [ -f "$filename" ]; then + curl -C - -o "$filename" "$@" "$url" + else + curl -o "$filename" "$@" "$url" + fi +else + die "Aucune méthode de téléchargement n'a été détectée" +fi diff --git a/legacy/.udir b/legacy/.udir new file mode 100644 index 0000000..5bc4561 --- /dev/null +++ b/legacy/.udir @@ -0,0 +1,6 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Utiliser 'udir --help-vars' pour une description de la signification des +# variables suivantes: +udir_desc="Fichiers de utools pour faciliter la migration des anciennes applications" +udir_note="" +udir_types=() diff --git a/legacy/instinc/base b/legacy/instinc/base new file mode 100644 index 0000000..068dd1d --- /dev/null +++ b/legacy/instinc/base @@ -0,0 +1,37 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +DEFAULT_DESTDIR=/usr/local +DEFAULT_DEPLOY='# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Copie des fichiers +copy_files=true +copy_vcs=false +destdir="'"$DEFAULT_DESTDIR"'" +srcdir=. +files= +owner="root:" +modes="u=rwX,g=rX,o=rX" + +# Mise à jour des inclusions +update_inc=false +update_inc_options= +update_inc_args=. + +# Variables configurables +configure_variables="\ +dest +" +configure_dest_for= + +# Scripts de configuration avant deploiement (utilisateur) +# Ces scripts sont lancés dans une copie de travail du répertoire à déployer, +# avant que les fichiers ne soient copiés dans la destination finale. +config_scripts= + +# Installation des profils +install_profiles=false +profiledir=lib/profile.d +bashrcdir=lib/bashrc.d +defaultdir=lib/default + +# Scripts de configuration après deploiement (root) +root_scripts=' diff --git a/legacy/instinc/envsetup b/legacy/instinc/envsetup new file mode 100644 index 0000000..e30b7a8 --- /dev/null +++ b/legacy/instinc/envsetup @@ -0,0 +1,66 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function __envsetup_init() { + SYSTEM_NAME="`uname -s`" + test "${SYSTEM_NAME#CYGWIN}" != "$SYSTEM_NAME" && SYSTEM_NAME=Cygwin + test "${SYSTEM_NAME#MINGW32}" != "$SYSTEM_NAME" && SYSTEM_NAME=Mingw + BASEHOST="${HOSTNAME%%.*}" +} + +function __envsetup_check_file() { + # note: pour utiliser cette fonction, les variables SYSTEM_NAME et BASEHOST + # doivent être définies. C'est le cas si sysinc/base a été inclus. + local dir="$1" file="$2" + + [ -f "$dir/$file" ] || return 1 + + # ignorer les fichiers qui ont l'extension .ignore + [ "${file%%.ignore}" != "$file" ] && return 1 + + # tester si le fichier est spécifique à une plateforme + system="$(expr "$file" : ".*\\.\\[\\([^.]*\\)\\]")" + [ -n "$system" -a "$system" != "$SYSTEM_NAME" ] && return 1 + system="$(expr "$file" : ".*\\.\\[-\\([^.]*\\)\\]")" + [ -n "$system" -a "$system" == "$SYSTEM_NAME" ] && return 1 + + # tester si le fichier est spécifique à une machine + host="$(expr "$file" : ".*\\.on_\\([^.]*\\)")" + [ -n "$host" -a "$host" != "$BASEHOST" ] && return 1 + host="$(expr "$file" : ".*\\.noton_\\([^.]*\\)")" + [ -n "$host" -a "$host" == "$BASEHOST" ] && return 1 + + # tester si le fichier est spécifique à un utilisateur + user="$(expr "$file" : ".*\\.for_\\([^.]*\\)")" + [ -n "$user" -a "$user" != "$USER" ] && return 1 + user="$(expr "$file" : ".*\\.notfor_\\([^.]*\\)")" + [ -n "$user" -a "$user" == "$USER" ] && return 1 + + return 0 +} + +function __envsetup_source_dir() { + # charger les fichiers qui sont dans le répertoire $1 (basedir). Si un + # fichier .source_in_order existe dans ce répertoire, il fixe l'ordre dans + # lesquel les fichiers sont chargés + local dir="$1" sio + + [ -d "$1" ] || return + + sio="$dir/.source_in_order" + if [ -f "$sio" ]; then + source "$sio" + else + local file user host system + for file in $(/bin/ls -1 "$dir" | while read file; do __envsetup_check_file "$dir" "$file" && echo "$dir/$file"; done); do + [ -z "$file" ] && continue + source "$file" + done + fi +} + +function __envsetup_terminate() { + unset -f __envsetup_init + unset -f __envsetup_check_file + unset -f __envsetup_source_dir + unset -f __envsetup_terminate +} diff --git a/legacy/instinc/envsetup_update b/legacy/instinc/envsetup_update new file mode 100644 index 0000000..0d90b86 --- /dev/null +++ b/legacy/instinc/envsetup_update @@ -0,0 +1,124 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require sysinc/functions +##@require envsetup + +envsetup_tmpfile= +function envsetup_init() { + ac_set_tmpfile envsetup_tmpfile +} + +function move_after() { + # dans le fichier $1, déplacer la ligne $2 après la ligne $3. Utiliser $4 + # comme fichier temporaire. + # si $3==*, mettre la ligne $2 en dernier + + if [ "$3" != "*" ] && ! quietgrep "$3" "$1"; then + # la ligne "$3" doit exister + return + fi + + uawk <"$1" >"$4" -v line="$2" -v after="$3" ' +BEGIN { + found_line = 0 + found_after = 0 +} +! found_line && $0 == line { + found_line = 1 + if (! found_after) { + next + } +} +! found_after && $0 == after { + found_after = 1 + if (found_line) { + # si nous avons trouvé la ligne avant after, la mettre juste après + # sinon, par la peine de faire de modification + print + print line + next + } +} +{ print } +END { + if (! found_after && after == "*") { + if (found_line) { + print line + } + } +} +' + /bin/cp "$4" "$1" +} + +function move_before() { + # dans le fichier $1, déplacer la ligne $2 avant la ligne $3. Utiliser $4 + # comme fichier temporaire + # si $3==*, mettre la ligne $2 en premier + + uawk <"$1" >"$4" -v line="$2" -v before="$3" ' +BEGIN { + found_line = 0 + found_before = 0 +} +! found_line && $0 == line { + found_line = 1 + if (found_before) { + next + } +} +! found_before && ($0 == before || before == "*") { + found_before = 1 + if (! found_line) { + print line + print + next + } +} +{ print } +' + /bin/cp "$4" "$1" +} + +envsetup_update_dir() { + # mettre à jour un répertoire qui contient des inclusions. $1=le répertoire, + # $2 un fichier temporaire qui est utilisé par cette fonction, $3=le + # répertoire de destination, qui vaut $1 par défaut + local system host user before after dir destdir file sio tmpfile + + tmpfile="$2" + dir="$(cd "$1"; pwd)" + destdir="${3:-$dir}" + sio="$dir/.source_in_order" + + >"$sio" + list_files "$dir" | while read file; do + __envsetup_check_file "$dir" "$file" && echo "$file" >>"$sio" + done + + echo "$(< "$sio")" | while read file; do + [ -z "$file" ] && continue + + before="$(awk <"$dir/$file" ' +$0 ~ /^##@before / { + print $2 + exit 0 +} +')" + if [ -n "$before" ]; then + move_before "$sio" "$file" "$before" "$tmpfile" + fi + + after="$(awk <"$dir/$file" ' +$0 ~ /^##@after / { + print $2 + exit 0 +} +')" + if [ -n "$after" ]; then + move_after "$sio" "$file" "$after" "$tmpfile" + fi + done + + uawk <"$sio" >"$tmpfile" -v destdir="$destdir" '{ print "source " destdir "/" $0 }' + /bin/cp "$tmpfile" "$sio" +} diff --git a/legacy/instinc/httpd_profile b/legacy/instinc/httpd_profile new file mode 100644 index 0000000..b2c881c --- /dev/null +++ b/legacy/instinc/httpd_profile @@ -0,0 +1,64 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +case "$httpd_profile" in +macosx+apache2.2) + logdir=/var/log/apache2 + apachedir=/etc/apache2 + apachectl=apachectl + confname=httpd.conf + avsitesname= + sitesname=sites + htdocsdir=/Library/WebServer/Documents + ;; +macosx+apache1.3) + logdir=/var/log/httpd + apachedir=/etc/httpd + apachectl=apachectl + confname=httpd.conf + avsitesname= + sitesname=sites + htdocsdir=/Library/WebServer/Documents + ;; +etch+apache2.2) + logdir=/var/log/apache2 + apachedir=/etc/apache2 + apachectl=apache2ctl + confname=apache2.conf + avsitesname=sites-available + sitesname=sites-enabled + htdocsdir=/var/www + ;; +etch+apache1.3) + logdir=/var/log/apache + apachedir=/etc/apache + apachectl=apachectl + confname=httpd.conf + avsitesname= + sitesname= + htdocsdir=/var/www + ;; +redhat5+apache2.2) + logdir=/var/log/httpd + apachedir=/etc/httpd + apachectl=apachectl + confname=conf/httpd.conf + avsitesname= + sitesname=sites + htdocsdir=/var/www/html + ;; +*) + # valeurs par défaut, si aucun profil connu n'est défini + # correspond à la configuration macosx+apache1.3 + logdir=/var/log/httpd + apachedir=/etc/httpd + apachectl=apachectl + confname=httpd.conf + avsitesname= + sitesname=sites + htdocsdir=/Library/WebServer/Documents + ;; +esac + +apacheconf="$apachedir/$confname" +avsitesdir="${avsitesname:+$apachedir/$avsitesname}" +sitesdir="$apachedir/$sitesname" diff --git a/legacy/instinc/prefixes b/legacy/instinc/prefixes new file mode 100644 index 0000000..c7fcb03 --- /dev/null +++ b/legacy/instinc/prefixes @@ -0,0 +1,415 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require sysinc/base +##@require sysinc/functions +##@require sysinc/java + +if [ "$SYSTEM_NAME" == "Darwin" ]; then + # valeurs par défaut de NEXT_ROOT, WOROOT et LOCALROOT sur MacOS X + DEFAULT_NEXT_ROOT= + function get_default_woroot() { echo $NEXT_ROOT/System; } + function get_default_localroot() { echo $NEXT_ROOT; } +else + # valeurs par défaut de NEXT_ROOT, WOROOT et LOCALROOT sur les autres Unix + DEFAULT_NEXT_ROOT=/opt/Apple + function get_default_woroot() { echo $NEXT_ROOT; } + function get_default_localroot() { echo $NEXT_ROOT/Local; } +fi + +function get_default_javahome() { + local GENTOO_VM JAVA_HOME JAVA JAVAC PATH="$PATH" + __select_default_java + local javahome + javahome="$JAVA_HOME" + + [ -n "$javahome" ] && echo "$javahome" +} + +function get_javaextdir() { + local javaextdir + if [ "$SYSTEM_NAME" == "Linux" ]; then + if [ -n "$JAVA_HOME" ]; then + javaextdir="$JAVA_HOME/jre/lib/ext" + [ -d "$javaextdir" ] || javaextdir="$JAVA_HOME/lib/ext" + fi + elif [ "$SYSTEM_NAME" == "Darwin" ]; then + javaextdir=/Library/Java/Extensions + fi + + [ -n "$javaextdir" -a -d "$javaextdir" ] && echo "$javaextdir" +} + +function get_apachebin() { + local i + case "$SYSTEM_NAME" in + Darwin|Linux) + for i in /usr/sbin/{apache2,apache,httpd}; do + if [ -x "$i" ]; then + echo "$i" + break + fi + done + ;; + SunOS) + for i in /usr{/local,}/apache/bin/{apache2,apache,httpd}; do + if [ -x "$i" ]; then + echo "$i" + break + fi + done + ;; + esac +} + +function get_apachectl() { + local i apachebin="${1:-$(get_apachebin)}" + for i in "$(dirname "$apachebin")"/apache*ctl; do + if [ -x "$i" ]; then + echo "$i" + break + fi + done +} + +function get_apacheversion() { + local apachebin="${1:-$(get_apachebin)}" + if [ -n "$apachebin" ]; then + local version="$($apachebin -v | grep version:)" + if [[ "$version" == *1.3* ]]; then + echo "" + elif [[ "$version" == *2.0* ]]; then + echo "2" + elif [[ "$version" == *2.2* ]]; then + echo "2.2" + fi + fi +} + +function get_default_apacheconfdir() { + # Calculer et afficher la valeur par défaut de APACHECONF_DIR, ou une chaine + # vide si l'on n'a pas pu le détecter automatiquement. + local i + case "$SYSTEM_NAME" in + Darwin) + echo /etc/httpd + ;; + Linux) + case "$LINUX_FLAVOUR" in + debian) + for i in /etc/{apache{2,,-ssl},httpd}; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + gentoo) + for i in /etc/{apache{2,},httpd}; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + *) + ;; + esac + ;; + SunOS) + for i in /usr{/local,}/apache/conf; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + esac +} + +function get_apacheconf() { + local i apacheconfdir="${1:-$(get_default_apacheconfdir)}" + for i in "$apacheconfdir"/{apache2,httpd}.conf; do + if [ -f "$i" ]; then + echo "$i" + break + fi + done +} + +function get_default_htdocsdir() { + # Calculer et afficher la valeur par défaut de HTDOCS_DIR, ou une chaine + # vide si l'on n'a pas pu le détecter automatiquement. + local i + case "$SYSTEM_NAME" in + Darwin) + echo "/Library/WebServer/Documents" + ;; + Linux) + case "$LINUX_FLAVOUR" in + debian) + for i in /var/www; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + gentoo) + for i in /var/www/localhost/htdocs; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + redhat) + for i in /var/www/html; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + *) + ;; + esac + ;; + SunOS) + for i in /usr{/local,}/apache/htdocs; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + esac +} + +function get_default_cgibindir() { + # Calculer et afficher la valeur par défaut de CGIBIN_DIR, ou une chaine + # vide si l'on n'a pas pu le détecter automatiquement. + local i + case "$SYSTEM_NAME" in + Darwin) + echo "/Library/WebServer/CGI-Executables" + ;; + Linux) + case "$LINUX_FLAVOUR" in + debian) + for i in /usr/lib/cgi-bin; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + gentoo) + for i in /var/www/localhost/cgi-bin; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + *) + ;; + esac + ;; + SunOS) + for i in /usr{/local,}/apache/cgi-bin; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + esac +} + +function get_default_ldapconfdir() { + # Calculer et afficher la valeur par défaut de LDAPCONF_DIR, ou une chaine + # vide si l'on n'a pas pu le détecter automatiquement. + local i + case "$SYSTEM_NAME" in + Darwin) + ;; + Linux) + case "$LINUX_FLAVOUR" in + debian) + for i in /etc/ldap; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + gentoo) + for i in /etc/openldap; do + if [ -d "$i" ]; then + echo "$i" + break + fi + done + ;; + *) + ;; + esac + ;; + SunOS) + ;; + esac +} + +function get_default_ldapowner() { + # Calculer et afficher la valeur par défaut de LDAPOWNER, ou une chaine + # vide si l'on n'a pas pu le détecter automatiquement. + local i + case "$SYSTEM_NAME" in + Darwin) + ;; + Linux) + case "$LINUX_FLAVOUR" in + debian) + echo "openldap:openldap" + ;; + gentoo) + echo "ldap:ldap" + ;; + *) + ;; + esac + ;; + SunOS) + ;; + esac +} + +function compute_prefixes() { + NEXT_ROOT="${NEXT_ROOT:-$DEFAULT_NEXT_ROOT}" + WOROOT="${WOROOT:-$(get_default_woroot)}" + LOCALROOT="${LOCALROOT:-$(get_default_localroot)}" + SYSTEMFRAMEWORKS="$WOROOT/Library/Frameworks" + WOEXTENSIONS="$LOCALROOT/Library/WebObjects/Extensions" + WOFRAMEWORKS="$LOCALROOT/Library/WebObjects/Applications/Frameworks" + WOAPPLICATIONS="$LOCALROOT/Library/WebObjects/Applications" + WOCONFIGURATION="$LOCALROOT/Library/WebObjects/Configuration" + + JAVA_HOME="${JAVA_HOME:-$(get_default_javahome)}" + JAVAEXTENSIONS="$(get_javaextdir)" + + APACHEBIN="${APACHEBIN:-$(get_apachebin)}" + APACHEVERSION="${APACHEVERSION:-$(get_apacheversion "$APACHEBIN")}" + APACHECTL="${APACHECTL:-$(get_apachectl "$APACHEBIN")}" + APACHECONF_DIR="${APACHECONF_DIR:-$(get_default_apacheconfdir)}" + APACHECONF="${APACHECONF:-$(get_apacheconf "$APACHECONF_DIR")}" + HTDOCS_DIR="${HTDOCS_DIR:-$(get_default_htdocsdir)}" + CGIBIN_DIR="${CGIBIN_DIR:-$(get_default_cgibindir)}" + + LDAPCONF_DIR="${LDAPCONF_DIR:-$(get_default_ldapconfdir)}" + LDAPOWNER="${LDAPOWNER:-$(get_default_ldapowner)}" +} + +PREFIXES=(NEXT_ROOT WOROOT LOCALROOT SYSTEMFRAMEWORKS WOEXTENSIONS WOFRAMEWORKS WOAPPLICATIONS WOCONFIGURATION +JAVA_HOME JAVAEXTENSIONS +APACHEBIN APACHEVERSION APACHECTL APACHECONF_DIR APACHECONF HTDOCS_DIR CGIBIN_DIR +LDAPCONF_DIR LDAPOWNER) +function NEXT_ROOT_value() { + [ -z "$NEXT_ROOT" ] && compute_prefixes + echo "$NEXT_ROOT" +} +function WOROOT_value() { + [ -z "$WOROOT" ] && compute_prefixes + echo "$WOROOT" +} +function LOCALROOT_value() { + [ -z "$LOCALROOT" ] && compute_prefixes + echo "$LOCALROOT" +} +function SYSTEMFRAMEWORKS_value() { + [ -z "$SYSTEMFRAMEWORKS" ] && compute_prefixes + echo "$SYSTEMFRAMEWORKS" +} +function WOEXTENSIONS_value() { + [ -z "$WOEXTENSIONS" ] && compute_prefixes + echo "$WOEXTENSIONS" +} +function WOFRAMEWORKS_value() { + [ -z "$WOFRAMEWORKS" ] && compute_prefixes + echo "$WOFRAMEWORKS" +} +function WOAPPLICATIONS_value() { + [ -z "$WOAPPLICATIONS" ] && compute_prefixes + echo "$WOAPPLICATIONS" +} +function WOCONFIGURATION_value() { + [ -z "$WOCONFIGURATION" ] && compute_prefixes + echo "$WOCONFIGURATION" +} +function JAVA_HOME_value() { + [ -z "$JAVA_HOME" ] && compute_prefixes + echo "$JAVA_HOME" +} +function JAVAEXTENSIONS_value() { + [ -z "$JAVAEXTENSIONS" ] && compute_prefixes + echo "$JAVAEXTENSIONS" +} +function APACHEBIN_value() { + [ -z "$APACHEBIN" ] && compute_prefixes + echo "$APACHEBIN" +} +function APACHEVERSION_value() { + [ -z "$APACHEVERSION" ] && compute_prefixes + echo "$APACHEVERSION" +} +function APACHECTL_value() { + [ -z "$APACHECTL" ] && compute_prefixes + echo "$APACHECTL" +} +function APACHECONF_DIR_value() { + [ -z "$APACHECONF_DIR" ] && compute_prefixes + echo "$APACHECONF_DIR" +} +function APACHECONF_value() { + [ -z "$APACHECONF" ] && compute_prefixes + echo "$APACHECONF" +} +function HTDOCS_DIR_value() { + [ -z "$HTDOCS_DIR" ] && compute_prefixes + echo "$HTDOCS_DIR" +} +function CGIBIN_DIR_value() { + [ -z "$CGIBIN_DIR" ] && compute_prefixes + echo "$CGIBIN_DIR" +} +function LDAPCONF_DIR_value() { + [ -z "$LDAPCONF_DIR" ] && compute_prefixes + echo "$LDAPCONF_DIR" +} +function LDAPOWNER_value() { + [ -z "$LDAPOWNER" ] && compute_prefixes + echo "$LDAPOWNER" +} + +function list_prefixes() { + local prefix + for prefix in "${PREFIXES[@]}"; do + echo "$prefix" + done +} + +function expand_prefix() { + local prefix + for prefix in "${PREFIXES[@]}"; do + if beginswith "$1" "$prefix/" || [ "$1" == "$prefix" ]; then + echo "$(${prefix}_value)${1#$prefix}" + return + fi + done + echo "$1" +} + +function dump_prefixes() { + local prefix + for prefix in "${PREFIXES[@]}"; do + echo "$prefix=$(expand_prefix "$prefix")" + done +} diff --git a/legacy/instinc/uinstdir b/legacy/instinc/uinstdir new file mode 100644 index 0000000..0f89b59 --- /dev/null +++ b/legacy/instinc/uinstdir @@ -0,0 +1,23 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function has_args() { + local arg + for arg in "$@"; do + case "$arg" in + -*) ;; + *) return 0;; + esac + done + return 1 +} + +function get_uinstdir() { + local p="$(pwd)" + while [ "$p" != "$HOME" -a "$p" != "" ]; do + if [ -f "$p/.uinst.conf" ]; then + echo "$p" + break + fi + p="${p%/*}" + done +} diff --git a/legacy/instinc/wobase b/legacy/instinc/wobase new file mode 100644 index 0000000..e8961a9 --- /dev/null +++ b/legacy/instinc/wobase @@ -0,0 +1,172 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require instinc/prefixes + +###################################################################### +# Chemins + +# répertoires relatifs pour les applications +APP_SRVRESDIR="Contents/Resources" +APP_SRVJAVADIR="Contents/Resources/Java" +APP_CLTRESDIR="Contents/WebServerResources" +APP_CLTJAVADIR="Contents/WebServerResources/Java" +APP_UNIXCLSPTHDIR="Contents/UNIX" +APP_UNIXCLSPTH="Contents/UNIX/UNIXClassPath.txt" +APP_MACOSCLSPTHDIR="Contents/MacOS" +APP_MACOSCLSPTH="Contents/MacOS/MacOSClassPath.txt" +APP_WINCLSPTHDIR="Contents/Windows" +APP_WINCLSPTH="Contents/Windows/CLSSPATH.txt" +APP_HTDOCSDIR="WebObjects" + +# répertoire relatifs pour les frameworks +FW_SRVRESDIR="Resources" +FW_SRVJAVADIR="Resources/Java" +FW_CLTRESDIR="WebServerResources" +FW_CLTJAVADIR="WebServerResources/Java" +FW_HTDOCSDIR="WebObjects/Frameworks" + +function create_wodirs_maybe() { + mkdir -p "$WOAPPLICATIONS" + mkdir -p "$WOFRAMEWORKS" +} + +###################################################################### +# Informations sur des répertoires de projet et des bundles + +function is_wosrcdir() { + # retourner true si le répertoire $1 est un répertoire de projet webobjects + + # tester d'abord l'existence du répertoire + [ -d "$1" ] || return 1 + + # tester la présence de wobuild.conf + [ -f "$1/wobuild.conf" ] && return 0 + + # tester la présence d'un ou plusieurs répertoires *.pbproj + [ -n "$(list_dirs "$1" "*.pbproj")" ] && return 0 + + # tester la présence d'un ou plusieurs répertoires *.xcode + [ -n "$(list_dirs "$1" "*.xcode")" ] && return 0 + [ -n "$(list_dirs "$1" "*.xcodeproj")" ] && return 0 + + # tester la présence de fichiers PB.project + [ -n "$(list_files "$1" "PB.project")" ] && return 0 + + # tester la présence de build.properties + [ -f "$1/build.properties" ] && return 0 + + # perdu... + return 1 +} + +function set_wobindir() { + # $1 étant le répertoire de projet, initialiser $2 avec le répertoire + # binaire correspondant, et $3 avec la description du projet + local project_name project_type project_desc + local wobindir__ + + if [ ! -f "$1/build.properties" ]; then + die "Ne supporte que les projets de type wobuild" + fi + file_get_properties "$1/build.properties" project.name "" project.type "framework" project.desc "" + if [ -z "project_name" -a -z "project_type" -a -z "project_desc" ]; then + file_get_properties "$1/build.properties" project_name "" project_type "application" project_desc "" + fi + if [ -z "$project_name" -o -z "$project_type" ]; then + die "Projet invalide: il faut spécifier project.name et project.type" + fi + if beginswith "$project_type" application; then + if [ -d "$1/dist/$project_name.woa" ]; then + wobindir__="$1/dist/$project_name.woa" + elif [ -d "$1/build/$project_name.woa" ]; then + wobindir_="$1/build/$project_name.woa" + else + wobindir__="$1/dist/$project_name.woa" + fi + elif beginswith "$project_type" framework; then + if [ -d "$1/dist/$project_name.framework" ]; then + wobindir__="$1/dist/$project_name.framework" + elif [ -d "$1/build/$project_name.framework" ]; then + wobindir_="$1/build/$project_name.framework" + else + wobindir__="$1/dist/$project_name.framework" + fi + else + die "Type de projet invalide: $project_type" + fi + if [ ! -d "$1" ]; then + die "Il faut construire le projet d'abord" + fi + + [ -n "$2" ] && set_var "$2" "$wobindir__" + [ -n "$3" ] && set_var "$3" "$project_desc" +} + +function is_woappdir() { + # retourner true si le répertoire $1 est un répertoire d'application + # webobjects + + # tester d'abord l'existence du répertoire + [ -d "$1" ] || return 1 + + # tester la présence des répertoire de resources + test -d "$1/$APP_SRVRESDIR" || return 1 + #test -d "$1/$APP_CLTRESDIR" || return 1 + + # tester la présence des répertoires de classpath + test -d "$1/$APP_UNIXCLSPTHDIR" || return 1 + test -d "$1/$APP_MACOSCLSPTHDIR" || return 1 + test -d "$1/$APP_WINCLSPTHDIR" || return 1 + + # on se contentera de ces tests sommaires... + return 0 +} + +function is_wofwdir() { + # retourner true sir le répertoire $1 est un répertoire de framework + # webobjects + + # tester d'abord l'existence du répertoire + [ -d "$1" ] || return 1 + + # tester les présence d'un des répertoires de resources + test -d "$1/$FW_SRVRESDIR" -o -d "$1/$FW_CLTRESDIR" || return 1 + + # on se contentera de ces tests sommaires... + return 0 +} + +function get_jawotools_properties_path() { + if [ -f "$1/jawotools.properties" ]; then + echo "$1/jawotools.properties" + elif [ -f "$1/build_infos.txt" ]; then + echo "$1/build_infos.txt" + else + echo "$1/jawotools.properties" + fi + + # jawotools.properties: description, developmentSnapshot, releaseDate, version + # build_infos.txt: desc, devel, date, version +} + +function read_jawotools_properties() { + local file="$(basename "$1")" + if [ "$file" == "jawotools.properties" ]; then + file_get_properties "$1" description "--NOT-SET--" version "--NOT-SET--" releaseDate "--NOT-SET--" developmentSnapshot "--NOT-SET--" + elif [ "$file" == "build_infos.txt" ]; then + local desc date devel + file_get_properties "$1" desc "--NOT-SET--" version "--NOT-SET--" date "--NOT-SET--" devel "--NOT-SET--" + description="$desc" + releaseDate="$date" + developmentSnapshot="$devel" + fi +} + +function get_version_txt_path() { + if [ -f "$1/VERSION.txt" ]; then + echo "$1/VERSION.txt" + elif [ -f "$1/version.txt" ]; then + echo "$1/version.txt" + else + echo "$1/VERSION.txt" + fi +} diff --git a/legacy/instinc/woconf b/legacy/instinc/woconf new file mode 100644 index 0000000..ac25880 --- /dev/null +++ b/legacy/instinc/woconf @@ -0,0 +1,194 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require instinc/prefixes +##@require instinc/wobase +##@require instinc/wofunctions + +function get_default_configfile() { + # $1=resdir (woadir/Contents/Resources ou fwkdir/Resources) + local configs configfile + array_from_lines configs "$(list_files "$1" "*.config")" + if [ ${#configs[*]} -eq 0 ]; then + array_from_lines configs "$(list_files "$1" "config.xml")" + fi + echo "${configs:-Properties}" +} + + +function set_config_property() { + # Modifier la propriété $2 avec la valeur $3 du fichier de configuration par + # défaut de l'application $1. On peut préciser le nom du fichier de + # configuration à utiliser avec $4. Par défaut, il s'agit du premier fichier + # ayant l'extension .config. Il doit s'agir d'un fichier de propriétés java. + + # si $2 vaut "-", $3 doit être de la forme "[configfile/]prop=value" ou + # ">configfile[:srcfile]" + + local action=setprop + local bundir="$1" prop="$2" value="$3" configfile="$4" + local srvresdir="$APP_SRVRESDIR" + if is_wofwdir "$bundir"; then + srvresdir="$FW_SRVRESDIR" + fi + + if [ "$prop" == "-" ]; then + if beginswith "$value" ">"; then + # choix d'un fichier de configuration par défaut + action=setconfigfile + configfile="${value#>}" + prop= + if [ "${configfile//:/}" != "$configfile" ]; then + # on a donné un fichier source + prop="copysrcfile" + value="${configfile##*:}" + configfile="${configfile%:*}" + fi + else + # modification de la valeur d'une propriété + prop="${value%%=*}" + if [ "${prop//\//}" != "$prop" ]; then + # on a donné le nom du fichier de propriété + configfile="${prop%/*}" + prop="${prop##*/}" + fi + value="${value#*=}" + fi + fi + if [ -z "$configfile" ]; then + if [ -n "$DEFAULT_CONFIGFILE" ]; then + configfile="$DEFAULT_CONFIGFILE" + else + configfile="$(get_default_configfile "$bundir/$srvresdir")" + fi + fi + + if [ "$action" == "setprop" ]; then + dest="$configfile" + first_char_is "$dest" "/" || dest="$bundir/$srvresdir/$dest" + mkdirof "$dest" + file_set_properties -c "$dest" "$prop" "$value" + elif [ "$action" == "setconfigfile" ]; then + if [ "$prop" == "copysrcfile" ]; then + src="$value" + dest="$configfile" + # chemin absolus + first_char_is "$src" "/" || src="$bundir/$srvresdir/$src" + first_char_is "$dest" "/" || dest="$bundir/$srvresdir/$dest" + mkdirof "$dest" + /bin/cp -f "$src" "$dest" + fi + DEFAULT_CONFIGFILE="$configfile" + fi +} + +function set_eomodel_property() { + : +} + +function woconf_display_help() { + echo "$scriptname: vérifier et modifier la configuration d'une application +ou d'un framework + +USAGE + $scriptname \"conf_cmds\" + +Les lignes de configuration peuvent être: + #commentaire -- un commentaire (ignoré) + v[erify] -- vérifier et corriger la consistance. + Cette action est faite automatiquement sauf + avec l'option --noverify. + c>configfile -- changer le fichier de configuration par + défaut + c>configfile:srcfile -- copier le fichier de configuration srcfile + sur configfile et choisir ce fichier comme + fichier de configuration par défaut + c[configfile/]prop=value -- modifier une propriété du fichier de + configuration. + === les options suivantes ne sont pas encore implémentées ================= + eprofile:user:password -- configurer l'eomodel par défaut selon le + profil + ehost:port:instance:user:passwd + -- configurer l'eomodel par défaut avec les + valeurs données + Ename.eomodeld:profile:user:password + -- configurer l'eomodel donné selon le profil + Ename.eomodeld:host:port:instance:user:password + -- configurer l'eomodel donné avec les valeurs + données +Les lignes de configuration suivantes sont spécifiques aux application (.woa) + aMyFramework[.framework] -- ajouter un framework + rMyFramework[.framework] -- enlever un framework + sbefore:after -- faire un recherche/remplacement + f[ix_case] -- corriger la casse du classpath selon les + frameworks installés" +} + +function woconf() { + local noverify= + + local end_of_options +##@include sysinc/begingetopt + -h|--help) + woconf_display_help + exit 0 + ;; + --no-verify) + # ne pas effectuer automatiquement la vérification du bundle + noverify=1 + ;; +##@include sysinc/endgetopt + + local tagdir="$1" srcdir bundir + + if [ -z "$tagdir" ]; then + die "Il faut spécifier l'application ou le framework à modifier" + fi + tagdir="$(abspath "$1")" + + if is_wosrcdir "$tagdir"; then + srcdir="$tagdir" + set_wobindir "$srcdir" bundir desc + + elif is_woappdir "$tagdir"; then + bundir="$tagdir" + + elif is_wofwdir "$tagdir"; then + bundir="$tagdir" + + else + die "Le bundle doit être un framework ou une application" + fi + + [ -w "$bundir" ] || die "Répertoire de bundle non accessible en écriture." + + bundir="${bundir%/}" + if [ -z "$noverify" ]; then + verifix_bundle "$bundir" + fi + + array_from_lines conf_cmds "$2" + for conf_cmd in "${conf_cmds[@]}"; do + action="$(first_char "$conf_cmd")" + option="$(last_chars "$conf_cmd")" + if [ "$action" == "#" ]; then + : # commentaire ignoré + elif [ "$action" == "v" ]; then + verifix_bundle "$bundir" + elif [ "$action" == "c" ]; then + set_config_property "$bundir" - "$option" + elif [ "$action" == "e" ]; then + set_eomodel_property "$bundir" - "$option" + elif [ "$action" == "a" ]; then + add_framework "$bundir" "$option" + elif [ "$action" == "r" ]; then + remove_framework "$bundir" "$option" + elif [ "$action" == "s" ]; then + search="${option%%:*}" + replace="${option#*:}" + searchreplace_classpath "$bundir" "$search" "$replace" + elif [ "$action" == "f" ]; then + fix_jars_case "$bundir" + else + eerror "Commande inconnue: $action" + fi + done +} diff --git a/legacy/instinc/wofunctions b/legacy/instinc/wofunctions new file mode 100644 index 0000000..c6264c0 --- /dev/null +++ b/legacy/instinc/wofunctions @@ -0,0 +1,212 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require instinc/wobase + +###################################################################### +# Informations sur des bundles + +function dump_frameworks() { + # Afficher les frameworks utilisés par une application + if [ "$SYSTEM_NAME" == "Darwin" ]; then + clspth="$APP_MACOSCLSPTH" + else + clspth="$APP_UNIXCLSPTH" + fi + cat "$1/$clspth" | nl2lf | grep '\.framework' | grep -v "^#" | grep -v "APPROOT/" | grep -v "WOROOT/" | awk ' +{ + if (match($0, /[^\/]*\.framework/) != 0) { + print substr($0, RSTART, RLENGTH) + } +}' | sort -u +} + +function get_app_winclspth() { + # calculer la valeur de APP_WINCLSPTH pour l'application $1 + if [ -f "$1/$APP_WINCLSPTH" ]; then + echo "$APP_WINCLSPTH" + else + local clspth + array_from_lines clspth "$(list_files "$1/$APP_WINCLSPTHDIR" | grep -i clsspath.txt)" + if [ ${#clspth[*]} -eq 1 ]; then + echo "$APP_WINCLSPTHDIR/$clspth" + else + echo "$APP_WINCLSPTH" + fi + fi +} + +###################################################################### +# Correction de bundles + +function verifix_bundle() { + # vérifier et corriger le bundle $1. Pour une application, on vérifie que le + # script est exécutable. Pour un framework, on vérifie que le framework est + # conforme au modèle des framework générés par WebObjects. + local bundir="$1" + if is_woappdir "$bundir"; then + local script="$bundir/$(basename "$bundir" .woa)" + if [ ! -x "$script" ]; then + chmod +x "$script" + fi + + elif is_wofwdir "$bundir"; then + [ -L "$bundir/Resources" ] && return + + local filename + + if [ ! -L "$bundir/Resources" ]; then + rm -rf "$bundir/Versions/A/Resources" + mkdir -p "$bundir/Versions/A/Resources" + list_all "$bundir/Resources" | while read filename; do + cp_R "$bundir/Resources/$filename" "$bundir/Versions/A/Resources" + done + rm -rf "$bundir/Resources" + fi + if [ ! -L "$bundir/WebServerResource" ]; then + rm -rf "$bundir/Versions/A/WebServerResources" + mkdir -p "$bundir/Versions/A/WebServerResources" + list_all "$bundir/WebServerResources" | while read filename; do + cp_R "$bundir/WebServerResources/$filename" "$bundir/Versions/A/WebServerResources" + done + rm -rf "$bundir/WebServerResources" + fi + local cwd="$(pwd)" + cd "$bundir/Versions" + if [ -e Current ]; then + if [ ! -L Current -a -d Current ]; then + rm -rf Current + else + rm -f Current + fi + fi + ln -s A Current + cd .. + ln -s Versions/Current/Resources Resources + ln -s Versions/Current/WebServerResources WebServerResources + cd "$cwd" + fi +} + +function add_framework() { + # ajouter le framework $2 (nom de base ou chemin absolu) à l'application + # dans le répertoire $1 + local bundir="$1" framework="$2" frameworks jars jar line + local app_winclspth="$(get_app_winclspth "$bundir")" + + endswith "$framework" .framework || framework="$framework.framework" + array_from_lines frameworks "$(dump_frameworks "$bundir")" + if ! array_contains frameworks "$framework"; then + if beginswith "$framework" /; then + # on a donné un chemin absolu + array_from_lines jars "$(list_files "$framework/$FW_SRVJAVADIR" "*.jar")" + if [ -z "${jars[*]}" ]; then + eerror "Impossible de trouver les fichiers du framework $framework +dans $WOFRAMEWORKS" + else + for jar in "${jars[@]}"; do + line="$framework/$FW_SRVJAVADIR/$jar" + echo "$jar" >>"$bundir/$APP_UNIXCLSPTH" + echo "$jar" >>"$bundir/$APP_MACOSCLSPTH" + echo "${jar//\//\\}" >>"$bundir/$app_winclspth" + done + fi + else + # on a donné juste le nom du framework + array_from_lines jars "$(list_files "$WOFRAMEWORKS/$framework/$FW_SRVJAVADIR" "*.jar")" + if [ -z "${jars[*]}" ]; then + eerror "Impossible de trouver les fichiers du framework $framework +dans $WOFRAMEWORKS" + else + for jar in "${jars[@]}"; do + line="LOCALROOT/Library/WebObjects/Applications/Frameworks/$framework/$FW_SRVJAVADIR/$jar" + echo "$line" >>"$bundir/$APP_UNIXCLSPTH" + echo "$line" >>"$bundir/$APP_MACOSCLSPTH" + echo "${line//\//\\}" >>"$bundir/$app_winclspth" + done + fi + fi + fi +} + +function searchreplace_classpath() { + # Dans les fichiers classpath de l'application $1, remplacer $2 par $3. Si + # $3 est vide, la ligne est supprimée + local bundir="$1" search="$2" replace="$3" scriptunx scriptwin + local app_winclspth="$(get_app_winclspth "$bundir")" + + [ -n "$search" ] || return 1 + search="${search//\//\\/}"; replace="${replace//\//\\/}" + if [ -n "$replace" ]; then + scriptunx="s/${search}/${replace}/g" + else + scriptunx="/${search}/d" + fi + search="${search//\\\//\\\\}"; replace="${replace//\\\//\\\\}" + if [ -n "$replace" ]; then + scriptwin="s/${search}/${replace}/g" + else + scriptwin="/${search}/d" + fi + + nl2lf "$bundir/$APP_UNIXCLSPTH"; sedi "$scriptunx" "$bundir/$APP_UNIXCLSPTH" + nl2lf "$bundir/$APP_MACOSCLSPTH"; sedi "$scriptunx" "$bundir/$APP_MACOSCLSPTH" + nl2lf "$bundir/$app_winclspth"; sedi "$scriptwin" "$bundir/$app_winclspth"; nl2crlf "$bundir/$app_winclspth" +} + +function remove_framework() { + # supprimer le framework $2 (nom de base) de l'application dans le + # répertoire $1 + local bundir="$1" framework="$2" frameworks + + endswith "$framework" .framework || framework="$framework.framework" + array_from_lines frameworks "$(dump_frameworks "$bundir")" + if array_contains frameworks "$framework"; then + searchreplace_classpath "$bundir" "$framework/" + fi +} + +function fix_jars_case() { + # Vérifier que la casse des jars de tous les frameworks utilisés par + # l'application $1 est conforme au système de fichier + local bundir="$1" clspth jars jar fwdir fwname jarname jarnamefs + if [ "$SYSTEM_NAME" == "Darwin" ]; then + clspth="$APP_MACOSCLSPTH" + else + clspth="$APP_UNIXCLSPTH" + fi + array_from_lines jars "$(cat "$bundir/$clspth" | nl2lf | grep '\.framework' | grep -v "^#" | grep -v "APPROOT/" | grep -v "WOROOT/" | awk ' +{ + if (match($0, /[^\/]*\.framework/) != 0) { + print + } +}' | sort -u)" + for jar in "${jars[@]}"; do + ! endswith "$jar" .jar && ! endswith "$jar" .zip && continue + + if beginswith "$jar" LOCALROOT; then + fwname="${jar##*/Frameworks/}"; fwname="${fwname%%/*}" + fwdir="$WOFRAMEWORKS" + else + fwdir="${jar%/*.framework/*}" + fwname="${jar##/*.framework/}"; + fwname="${jar:0:$((${#jar}-${#fwname}-1))}"; fwname="${fwname##*/}" + fi + jarname="${jar##*/}" + + if [ ! -d "$fwdir/$fwname" ]; then + eerror "$(basename "$bundir"): $fwname: ce framework n'existe pas" + continue + fi + + array_from_lines jarnamefs "$(list_files "$fwdir/$fwname/$FW_SRVJAVADIR" "*.jar" | grep -i "$jarname")" + if [ ${#jarnamefs[*]} -eq 0 ]; then + eerror "$(basename "$bundir"): $fwname: $jarname n'existe pas" + elif [ ${#jarnamefs[*]} -gt 1 ]; then + eerror "$(basename "$bundir"): $fwname: il existe plusieurs jars $jarname" + else + if [ "$jarname" != "$jarnamefs" ]; then + einfo "$(basename "$bundir"): $fwname: $jarnamefs" + searchreplace_classpath "$bundir" "$fwname/$FW_SRVJAVADIR/$jarname" "$fwname/$FW_SRVJAVADIR/$jarnamefs" + fi + fi + done +} diff --git a/legacy/instinc/womonitor b/legacy/instinc/womonitor new file mode 100644 index 0000000..f8d086b --- /dev/null +++ b/legacy/instinc/womonitor @@ -0,0 +1,570 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +###################################################################### +# Gestion des processus + +function wo_pidof() { + # obtenir le(s) pid de l'instance $1 + + # attention! on doit avoir un ps non legacy (cf sysinc/system_caps) + ps_all | grep "\-[W]OApplicationName $1 " | awk '{ print $1 }' + + # note: on met [W] entre crochet pour que le processus associé à grep ne + # soit pas pris dans la liste + + # note: on met un espace après le nom de l'application pour ne matcher que + # celle-là. +} + +###################################################################### +# Services basés sur le parcours du fichier SiteConfig.xml + +AWK_FUNCTIONS=' +function get_value(line) { + match(line, /^[^<]*<[^<>]*>/); line = substr(line, RSTART + RLENGTH) + match(line, /<.*$/); line = substr(line, 1, RSTART - 1) + return line +} + +function get_attr_value(attr, line) { + match(line, attr "=\""); line = substr(line, RSTART + RLENGTH) + match(line, /".*$/); line = substr(line, 1, RSTART - 1) + return line +} +' + +function siteconf_dump_hosts() { + # Retourner la liste des hôtes configurés + # Chaque ligne est de la forme: + # host type + [ -f "$WOCONFIGURATION/SiteConfig.xml" ] || return 1 + + awk <"$WOCONFIGURATION/SiteConfig.xml" "$AWK_FUNCTIONS"' +BEGIN { + in_hosts = 0 +} + +!in_hosts && $0 ~ // { in_hosts = 0 } + +in_hosts && $0 ~ // { + print name " " type +} + +in_hosts && $0 ~ // { in_applications = 0 } + +in_applications && $0 ~ // { + print name " " autoRecover " " debuggingEnabled " " path +} + +in_applications && $0 ~ // { in_instances = 0 } + +in_instances && $0 ~ // { + print applicationName " " id " " hostName " " port " " autoRecover " " debuggingEnabled " " path +} + +in_instances && $0 ~ /([^<]*)<.*$", "\\1", 1) + match($0, /^[^<]*<[^<>]*>/); $0 = substr($0, RSTART + RLENGTH) + match($0, /<.*$/); $0 = substr($0, 1, RSTART - 1) + print +} +' +} + +function siteconf_get_instance_data_for_updateWotaskd() { + # obtenir les informations au format XML pour les instances données, à + # utiliser avec + # Les informations sont retournées pour chaque minstance, une par ligne. + [ -f "$WOCONFIGURATION/SiteConfig.xml" ] || return 1 + [ -n "$*" ] || return 1 + + script=' +BEGIN { + found_instances = 0 + found_element = 0 +} + +$0 ~ /([^<]*)<.*$", "\\1", 1) + name = $0 + match(name, /^[^<]*<[^<>]*>/); name = substr(name, RSTART + RLENGTH) + match(name, /<.*$/); name = substr(name, 1, RSTART - 1) +} + +found_element && $0 ~ /<\/element/ { + if (' + + local instance notfirst= + for instance in "$@"; do + script="$script${notfirst:+|| }name == \"$(quote_awk "$instance")\"" + notfirst=1 + done + script="$script"') { + print data + } + found_element = 0 + next +} + +found_element { + match($0, /^[\t]*/) + $0 = substr($0, RSTART + RLENGTH) + data = data $0 +} + +found_instances && $0 ~ /<\/instanceArray/ { + found_element = 0 + found_instances = 0 +} +' + + awk <"$WOCONFIGURATION/SiteConfig.xml" "$script" +} + +function siteconf_get_instance_data_for_commandWotaskd() { + # obtenir les informations au format XML pour les instances données, à + # utiliser avec + # Les informations sont retournées pour chaque minstance, une par ligne. + [ -f "$WOCONFIGURATION/SiteConfig.xml" ] || return 1 + + script=' +BEGIN { + found_instances = 0 + found_element = 0 +} + +$0 ~ /([^<]*)<.*$", "\\1", 1) + id = $0 + match(id, /^[^<]*<[^<>]*>/); id = substr(id, RSTART + RLENGTH) + match(id, /<.*$/); id = substr(id, 1, RSTART - 1) +} +found_element && $0 ~ /([^<]*)<.*$", "\\1", 1) + port = $0 + match(port, /^[^<]*<[^<>]*>/); port = substr(port, RSTART + RLENGTH) + match(port, /<.*$/); port = substr(port, 1, RSTART - 1) +} +found_element && $0 ~ /([^<]*)<.*$", "\\1", 1) + name = $0 + match(name, /^[^<]*<[^<>]*>/); name = substr(name, RSTART + RLENGTH) + match(name, /<.*$/); name = substr(name, 1, RSTART - 1) +} +found_element && $0 ~ /([^<]*)<.*$", "\\1", 1) + hostName = $0 + match(hostName, /^[^<]*<[^<>]*>/); hostName = substr(hostName, RSTART + RLENGTH) + match(hostName, /<.*$/); hostName = substr(hostName, 1, RSTART - 1) +} + +found_element && $0 ~ /<\/element/ { + if (' + + local instance notfirst= + for instance in "$@"; do + script="$script${notfirst:+|| }name == \"$(quote_awk "$instance")\"" + notfirst=1 + done + script="$script"') { + print "" id "" port "" name "" hostName "" + } + found_element = 0 + next +} + +found_element { + match($0, /^[\t]*/) + $0 = substr($0, RSTART + RLENGTH) + data = data $0 +} + +found_instances && $0 ~ /<\/instanceArray/ { + found_element = 0 + found_instances = 0 +} +' + + awk <"$WOCONFIGURATION/SiteConfig.xml" "$script" +} + +function get_autostart() { + # Afficher le chemin vers le fichier $WOCONFIGURATION/AutoStart.txt + echo "$WOCONFIGURATION/AutoStart.txt" +} + +function apply_autostart_order() { + # Reordonner les instances du tableau $1 selon la liste données dans le + # fichier $WOCONFIGURATION/AutoStart.txt + local autostart_="$(get_autostart)" + [ -r "$autostart_" ] || return + + local -a oinsts_ srcname_ dest_ + local oinst_ + array_from_lines oinsts_ "$(<"$autostart_" filter_conf)" + + srcname_="$1" + for oinst_ in "${oinsts_[@]}"; do + if array_contains "$srcname_" "$oinst_"; then + array_add dest_ "$oinst_" + array_del "$srcname_" "$oinst_" + fi + done + array_extend dest_ "$srcname_" + + array_copy "$srcname_" dest_ +} + +###################################################################### +# Services basés sur wotaskd, sans mot de passe + +function wotaskd_dump_woconfig() { + # afficher la configuration de wotaskd sur l'hôte $1 (par défaut + # $FQDNHOST). Il s'agit de la liste, pour chacune des instances, des + # minstances qui tournent actuellement. + # cette fonction n'a pas besoin de mot de passe pour fonctionner. + local host="${1:-$FQDNHOST}" + dumpurl -X "http://$host:1085/cgi-bin/WebObjects/wotaskd.woa/wa/woconfig" +} + +function wotaskd_dump_running_instances() { + wotaskd_dump_woconfig | awk "$AWK_FUNCTIONS"' +BEGIN { + in_application = 0 +} + +!in_application && $0 ~ // { in_application = 0 } + +in_application && $0 ~ /APPLICATION' "$password" "$host" +} + +function wotaskd_dump_minstances() { + # afficher la configuration des minstances de wotaskd sur un hôte. Il + # s'agit de la liste des instances qui sont configurées + eval "$(_getopt_host_and_password)" + wotaskd_command 'INSTANCE' "$password" "$host" +} + +function dump_minstances_stats_from_stdin() { + # Afficher les statistiques pour les instances de la ligne de commande à + # partir de la sortie de wotaskd_dump_minstances + # Les lignes affichées sont de la forme: + # applicationName id port runningState activeSessions transactions startedAt avgTransactionTime averageIdlePeriod + script="$AWK_FUNCTIONS"' +BEGIN { + in_instances = 0 +} + +!in_instances && $0 ~ // { in_instances = 0 } + +in_instances && $0 ~ // { + if (applicationName != ""' + local mapp filter + for mapp in "$@"; do + filter="${filter:+$filter || }applicationName == \"$mapp\"" + done + [ -n "$filter" ] && filter="&& ($filter)" + script="$script$filter"') { + print applicationName " " id " " port " " runningState " " activeSessions " " transactions " " startedAt " " avgTransactionTime " " averageIdlePeriod + } +} + +in_instances && $0 ~ /([^<]*)<.*$", "\\1", 1) + match($0, /^[^<]*<[^<>]*>/); $0 = substr($0, RSTART + RLENGTH) + match($0, /<.*$/); $0 = substr($0, 1, RSTART - 1) + print +} +' +} + +function wotaskd_command_instances_from_stdin() { + # lancer une commande pour les instances avec les lignes lues sur stdin + eval "$(_getopt_host_and_password)" + + local COMMAND="$1"; shift + wotaskd_command "$( +echo ''"$COMMAND"'' +while read element; do + echo ''"$element"'' +done +echo '' +)" "$password" "$host" >&"$outputfile" +} + +function wotaskd_update_instances_from_stdin() { + # Metttre à jour les instances avec les lignes lues sur stdin + eval "$(_getopt_host_and_password)" + + wotaskd_command "$( +echo '' +while read element; do + echo ''"$element"'' +done +echo '' +)" "$password" "$host" >&"$outputfile" +} diff --git a/legacy/instinc/worestart b/legacy/instinc/worestart new file mode 100644 index 0000000..1736ee3 --- /dev/null +++ b/legacy/instinc/worestart @@ -0,0 +1,88 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require instinc/wobase +##@require instinc/wofunctions +##@require instinc/womonitor + +function get_configuration() { + siteconf_get_instance_data_for_updateWotaskd "$@" +} + +function restore_configuration() { + echo "$1" | wotaskd_update_instances_from_stdin +} + +function enable_autoRecover() { + siteconf_get_instance_data_for_updateWotaskd "$@" | awk '{ +if (match($0, //) != 0) { + $0 = substr($0, 1, RSTART - 1) "YES" substr($0, RSTART + RLENGTH) +} +print +}' | wotaskd_update_instances_from_stdin +} + +function disable_autoRecover() { + siteconf_get_instance_data_for_updateWotaskd "$@" | awk '{ +if (match($0, //) != 0) { + $0 = substr($0, 1, RSTART - 1) "NO" substr($0, RSTART + RLENGTH) +} +if (match($0, //) != 0) { + $0 = substr($0, 1, RSTART - 1) "NO" substr($0, RSTART + RLENGTH) +} +print +}' | wotaskd_update_instances_from_stdin +} + + +###################################################################### +# Services de haut niveau + +function start_instances() { + # Lancer les instances dont on donne le nom + eval "$(_getopt_host_and_password)" + + outputfile="$(mktempf)" + siteconf_get_instance_data_for_commandWotaskd "$@" | wotaskd_command_instances_from_stdin -h "$host" -P "$password" -o "$outputfile" START + + local status=0 + if [ $(grep 'success.*YES' "$outputfile" | wc -l) -ne $# ]; then + #eerror "Une erreur s'est produite:" + #cat "$outputfile" + status=1 + fi + /bin/rm -f "$outputfile" + return $status +} + +function stop_instances() { + # Arrêter les instances dont on donne le nom + eval "$(_getopt_host_and_password)" + + outputfile="$(mktempf)" + siteconf_get_instance_data_for_commandWotaskd "$@" | wotaskd_command_instances_from_stdin -h "$host" -P "$password" -o "$outputfile" STOP + + local status=0 + if [ $(grep 'success.*YES' "$outputfile" | wc -l ) -ne $(($# + 1)) ]; then + #eerror "Une erreur s'est produite:" + #cat "$outputfile" + status=1 + fi + /bin/rm -f "$outputfile" + return $status +} + +function quit_instances() { + # Forcer à quiter les instances dont on donne le nom + eval "$(_getopt_host_and_password)" + + outputfile="$(mktempf)" + siteconf_get_instance_data_for_commandWotaskd "$@" | wotaskd_command_instances_from_stdin -h "$host" -P "$password" -o "$outputfile" QUIT + + local status=0 + if [ $(grep 'success.*YES' "$outputfile" | wc -l ) -ne $(($# + 1)) ]; then + #eerror "Une erreur s'est produite:" + #cat "$outputfile" + status=1 + fi + /bin/rm -f "$outputfile" + return $status +} diff --git a/legacy/instinc/wosign b/legacy/instinc/wosign new file mode 100644 index 0000000..2f3a035 --- /dev/null +++ b/legacy/instinc/wosign @@ -0,0 +1,184 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +WOSIGN_KEYSTORE= +WOSIGN_STOREPASS= +WOSIGN_KEYALIAS= + +function wosign_setup_maybe() { + if [ -f "$WOCONFIGURATION/Signatures/signature.conf" ]; then + local keystore storepass keyalias + file_get_vars "$WOCONFIGURATION/Signatures/signature.conf" keystore "" storepass "" keyalias "" + WOSIGN_KEYSTORE="$keystore" + WOSIGN_STOREPASS="$storepass" + WOSIGN_KEYALIAS="$keyalias" + + [ -n "$WOSIGN_XTMPDIR" ] || ac_set_tmpdir WOSIGN_XTMPDIR + [ -n "$WOSIGN_JTMPDIR" ] || ac_set_tmpdir WOSIGN_JTMPDIR + return 0 + else + return 1 + fi +} + +function __issjar() { + [ "${1%.sjar}" != "$1" ] +} + +function __tosjar() { + local jarname="$(basename "$1")" + local jardir="${1%$jarname}" + local sjarname= jarbn="$(basename "$jarname" .jar)" + if [ "$jarbn" != "$jarname" ]; then + sjarname="$jarbn.sjar" + else + sjarname="$jarname.sjar" + fi + echo "$jardir$sjarname" +} + +function __tojar() { + local jarname="$(basename "$1")" + local jardir="${1%$jarname}" + local jarbn="$(basename "$jarname" .sjar)" + [ "$jarbn" != "$jarname" ] && jarname="$jarbn.jar" + echo "$jardir$jarname" +} + +function wosign_jar() { + local default=1 sign= unsign= + while [ -n "$1" ]; do + case "$1" in + -s) default=; sign=1;; + -d) default=; unsign=1;; + *) break;; + esac + shift + done + [ -n "$default" ] && sign=1 + + local curdir="$(pwd)" + local jar="$(abspath "$1")" + local cjar="$WOSIGN_JTMPDIR/$(basename "$jar")" + local sjar="$(__tosjar "$jar")" + + cd "$WOSIGN_XTMPDIR" + rm -rf * + jar xf "$jar" + rm -f META-INF/*.{SF,RSA,DSA} + + jar cf "$cjar" * + if [ -n "$unsign" ]; then + cp "$cjar" "$jar" + fi + + if [ -n "$sign" ]; then + rm -f "$sjar" + jarsigner -keystore "$WOSIGN_KEYSTORE" ${WOSIGN_STOREPASS:+-storepass "$WOSIGN_STOREPASS" }-signedjar "$sjar" "$cjar" $WOSIGN_KEYALIAS + fi + + cd "$curdir" +} + +function wosignable() { + if [ -z "$WOSIGN_KEYSTORE" ]; then + echo "Il faut spécifier le paramètre keystore" + return 1 + elif [ -z "$WOSIGN_KEYALIAS" ]; then + echo "Il faut spécifier le parammètre keyalias" + return 1 + fi + + local srcdir="$1" + if endswith "$srcdir" .woa; then + srcdir="$srcdir/$APP_CLTJAVADIR" + elif endswith "$1" .framework; then + srcdir="$srcdir/$FW_CLTJAVADIR" + fi + if [ -d "$srcdir" ]; then + if [ -z "$(list_files "$srcdir" "*.jar")" ]; then + echo "Il n'y a pas de jars à signer" + return 1 + fi + elif [ -f "$srcdir" ]; then + if ! endswith "$srcdir" .jar; then + echo "Le fichier spécifié n'est pas un jar" + return 1 + fi + else + echo "Il faut spécifier un répertoire ou un jar individuel" + return 1 + fi +} + +function __may_sign() { + # Si l'option -f est spécifiée, retourner true + # Si ce jar a une version signée associée, retourner true + # Si ce jar n'a pas de version signée associée, retourner true + # Si ce jar est la version signée d'un autre jar, retourner false + [ "$1" != "-f" ] && __issjar "$1" && [ -f "$(__tojar "$1")" ] && return 1 + return 0 +} + +function __should_sign() { + # Si l'option -f est spécifiée, retourner true + # Sinon retourner true si la version signée n'existe pas + # On assume que __may_sign est vrai. + [ "$1" != "-f" ] && [ -f "$(__tosjar "$1")" ] && return 1 + return 0 +} + +function wosign() { + # Signer un bundle, les jars d'un répertoire, ou un jar + # L'option -f force la resignature des jars d'un répertoire ou d'un + # bundle. Elle force aussi la signature d'un jar, même s'il semble qu'il + # soit la version signée d'un autre jar + # on présuppose que wosignable a retourné true + local default=1 sign= unsign= resign= + while [ -n "$1" ]; do + case "$1" in + -s) default=; sign=1;; + -d) default=; unsign=1;; + -f) resign=1;; + *) break;; + esac + shift + done + [ -n "$default" ] && sign=1 + + local srcdir="$1" + local candidates jars jar jarname jardir + + if endswith "$srcdir" .woa; then + srcdir="$srcdir/$APP_CLTJAVADIR" + elif endswith "$1" .framework; then + srcdir="$srcdir/$FW_CLTJAVADIR" + fi + + if [ -d "$srcdir" ]; then + array_from_lines candidates "$(list_files "$srcdir" "*.jar")" + jars=() + for jar in "${candidates[@]}"; do + __may_sign "$srcdir/$jar" && jars=("${jars[@]}" "$srcdir/$jar") + done + for jar in "${jars[@]}"; do + if __should_sign ${resign:+-f }"$jar"; then + ebegin "$(ppath "$jar")" + wosign_jar ${sign:+-s }${unsign:+-d }"$jar" & + ewait $! + eend + fi + done + elif [ -f "$srcdir" ]; then + jar="$srcdir" + if ! __may_sign ${resign:+-f }"$jar"; then + jardir="$(dirname "$jar")" + jarname="$(basename "$jar")" + eerror "$(ppath "$jar"): Ce jar est la version signée de $(ppath "$jardir/${jarname#s}")" + elif __should_sign ${resign:+-f }"$jar"; then + ebegin "$(ppath "$jar")" + wosign_jar ${sign:+-s }${unsign:+-d }"$jar" & + ewait $! + eend + fi + fi +} diff --git a/legacy/instinc/wotag b/legacy/instinc/wotag new file mode 100644 index 0000000..dbbce62 --- /dev/null +++ b/legacy/instinc/wotag @@ -0,0 +1,163 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require instinc/prefixes +##@require instinc/wobase +##@require instinc/wofunctions + +function wotag_display_help() { + echo "$scriptname: attacher une information de version à une application ou un framework + +USAGE + $scriptname " +} + +function wotag() { + local action=tag description version releaseDate developmentSnapshot message + local tagdir="$1" srcdir bundir srvresdir + + local end_of_options +##@include sysinc/begingetopt + -h|--help) + wotag_display_help + exit 0 + ;; + -c|--check-tagged) + action=check + ;; + -s|--show) + action=show + ;; + -v|--version) + # spécifier le numéro de version, ou - s'il n'y a pas de numéro de version + shift + version="$1" + ;; + -d|--date) + # spécifier la date de release + shift + releaseDate="$1" + ;; + -m|--desc) + # spécifier la description de l'application ou du framework + shift + description="$1" + ;; + --message) + shift + message="$1" + ;; +##@include sysinc/endgetopt + + if [ -z "$tagdir" ]; then + die "Il faut spécifier l'application ou le framework à tagger" + fi + tagdir="$(abspath "$1")" + if is_wosrcdir "$tagdir"; then + srcdir="$tagdir" + set_wobindir "$srcdir" bundir desc + + elif is_woappdir "$tagdir"; then + bundir="$tagdir" + + elif is_wofwdir "$tagdir"; then + bundir="$tagdir" + + else + [ "$action" == "show" ] && return 1 + die "Le bundle doit être un framework ou une application" + fi + + bundir="${bundir%/}" + if endswith "$bundir" .woa; then + local srvresdir="$APP_SRVRESDIR" + elif endswith "$bundir" .framework; then + local srvresdir="$FW_SRVRESDIR" + fi + local infosfile="$(get_jawotools_properties_path "$bundir")" + local infosfile2="$bundir/jawotools.properties" + local versionfile="$(get_version_txt_path "$bundir/$srvresdir")" + + if [ "$action" == "check" ]; then + if [ -f "$infosfile" ]; then + # vérifier la présence de description, version, releaseDate, developmentSnapshot + read_jawotools_properties "$infosfile" + if [ "$description" != "--NOT-SET--" -a "$version" != "--NOT-SET--" -a "$releaseDate" != "--NOT-SET--" -a "$developmentSnapshot" != "--NOT-SET--" ]; then + return 0 + fi + fi + return 1 + + elif [ "$action" == "show" ]; then + if [ -f "$infosfile" ]; then + read_jawotools_properties "$infosfile" + [ "$description" == "--NOT-SET--" ] && description= + [ "$version" == "--NOT-SET--" ] && version= + [ "$releaseDate" == "--NOT-SET--" ] && releaseDate= + [ "$developmentSnapshot" == "--NOT-SET--" ] && developmentSnapshot= + if [ -z "$version" -a -z "$releaseDate" ]; then + einfo "${message:-$(basename "$bundir")}: ?" + else + einfo "${message:-$(basename "$bundir")}: Version${version:+ $version}${releaseDate:+ du $releaseDate}${developmentSnapshot:+ (developmentSnapshot)}" + fi + else + einfo "${message:-$(basename "$bundir")}: ?" + fi + return 0 + + elif [ "$action" == "tag" ]; then + developmentSnapshot= + + if [ -z "$description" -a -f "$infosfile" ]; then + read_jawotools_properties "$infosfile" + fi + + if [ -f "$versionfile" ]; then + # si le fichier de version existe... + version="$(awk <"$versionfile" '{ + gsub("-r../../....$", "") + print + exit +}')" + releaseDate="$(awk <"$versionfile" '{ + if ($0 ~ /.*-r..\/..\/....$/) { + gsub(".*-r", "") + print + } + exit +}')" + developmentSnapshot= + if [ -n "$srcdir" -a -x "$scriptdir/reldiff" ]; then + "$scriptdir/reldiff" -p "$srcdir" -c && developmentSnapshot=1 + fi + + else + # pas de fichier de version. il faut saisir les valeurs à la main + version_or_date_flag=-i + [ \( -z "$version" -o "$version" == "-" \) -a -z "$releaseDate" ] && version_or_date_flag=-c + + read_value -i "Entrez la description" description "$description" N + if [ "$version" == "-" ]; then + version= + else + read_value $version_or_date_flag "Entrez la version (vide s'il n'y a que la date de release)" version "$version" N + [ "$version" == "-" ] && version= + fi + if [ -z "$releaseDate" ]; then + # date de release par défaut: la date du jour + releaseDate="$(date +"%d/%m/%Y")" + fi + read_value $version_or_date_flag "Entrez la date de release" releaseDate "$releaseDate" N + [ "$releaseDate" == "-" ] && releaseDate= + + if [ -z "$version" -a -z "$releaseDate" ]; then + die "Il faut avoir un numéro de version et/ou une date de release" + fi + fi + + einfo "${message:-$(basename "$bundir")}: Version${version:+ $version}${releaseDate:+ du $releaseDate}${developmentSnapshot:+ (developmentSnapshot)}" + [ "$infosfile" != "$infosfile2" ] && rm -f "$infosfile" + file_set_properties -c "$infosfile2" description "$description" version "$version" releaseDate "$releaseDate" developmentSnapshot "$developmentSnapshot" + + else + die "Action inconnue: $action" + fi +} diff --git a/legacy/sysinc/base b/legacy/sysinc/base new file mode 100644 index 0000000..f00898d --- /dev/null +++ b/legacy/sysinc/base @@ -0,0 +1,1033 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# base: fonctions et variables de base pour utilisation sous Linux. requière +# l'utilisation de bash + +##@provide sysinc/basevars + +# === Variables diverses === +# répertoire temporaire +export TMPDIR="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}" +# BASEHOST est le nom d'hote *sans* domaine +export BASEHOST="${HOSTNAME%%.*}" +# FQDNHOST est le nom d'hote avec le domaine +if [ "$BASEHOST" != "$HOSTNAME" ]; then + export FQDNHOST="$HOSTNAME" +else + export FQDNHOST="$HOSTNAME.$(dnsdomainname)" +fi +# emplacement du script courant +scriptname="$(basename -- "$0")" +scriptdir="$(cd "$(dirname -- "$0")"; pwd)" +script="$scriptdir/$scriptname" + +# fonction pour charger automatiquement /etc/default/$name et ~/etc/default/$name +function set_defaults() { + local __f __s + for __f in "$@"; do + __s= + if [ -r "/etc/default/$__f" ]; then + source "/etc/default/$__f" + __s=1 + fi + if [ -r "$HOME/etc/default/$__f" ]; then + source "$HOME/etc/default/$__f" + __s=1 + fi + if [ -z "$__s" ]; then + # si pas de fichier dans ~/etc/default ni dans /etc/default, lire + # $scriptdir/lib/default/$scriptname en dernier lieu + if [ -r "$scriptdir/lib/default/$__f" ]; then + source "$scriptdir/lib/default/$__f" + fi + fi + done +} + +# === diverses fonctions === +function is_yes() { + # retourner vrai si $1 est une valeur "oui" + [ "$1" == "o" -o "$1" == "O" -o "$1" == "oui" -o "$1" == "OUI" ] && return 0 + [ "$1" == "y" -o "$1" == "Y" -o "$1" == "yes" -o "$1" == "YES" ] && return 0 + [ "$1" == "v" -o "$1" == "V" -o "$1" == "vrai" -o "$1" == "VRAI" ] && return 0 + [ "$1" == "t" -o "$1" == "T" -o "$1" == "true" -o "$1" == "TRUE" ] && return 0 + [ "$1" == "1" ] && return 0 + return 1 +} + +function is_no() { + # retourner vrai si $1 est une valeur "non" + [ "$1" == "n" -o "$1" == "N" -o "$1" == "non" -o "$1" == "NON" ] && return 0 + [ "$1" == "no" -o "$1" == "NO" ] && return 0 + [ "$1" == "f" -o "$1" == "F" -o "$1" == "faux" -o "$1" == "FAUX" ] && return 0 + [ "$1" == "false" -o "$1" == "FALSE" ] && return 0 + [ "$1" == "0" ] && return 0 + return 1 +} + +# retourner le premier caractère de la chaine $1 +function first_char() { echo "${1:0:1}"; } +# retourner le dernier caractère de la chaine $1 +function last_char() { echo "${1:$((-1)):1}"; } +# retourner tous les caractères de la chaine $1, excepté le dernier +function first_chars() { echo "${1:0:$((${#1}-1))}"; } +# retourner tous les caractères de la chaine $1, excepté le premier +function last_chars() { echo "${1:1}"; } +# Tester si le premier caractère de la chaine $1 est $2 +function first_char_is() { test "${1:0:1}" = "$2"; } +# Tester si le dernier caractère de la chaine $1 est $2 +function last_char_is() { test "${1:$((-1)):1}" = "$2"; } +# Tester si la chaine $1 commence par le wildcard $2 +function beginswith() { eval 'test "${1#'"$(quote_arg "$2")"'}" != "$1"'; } +# Tester si la chaine $1 se termine par le wildcard $2 +function endswith() { eval 'test "${1%'"$(quote_arg "$2")"'}" != "$1"'; } +# Dans la chaine $1, remplacer \ par \\, " par \" et $ par \$, et l'afficher +function quote_arg() { + local s + s="${1//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//$/\\$}" + echo "$s" + ## ou: + #echo "$1" | awk '{ gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\$/, "\\$"); print }' +} +# Dans une chaine lue sur stdin, remplacer \ par \\, " par \" et $ par \$ et +# l'afficher +function quote_string() { + awk '{ gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\$/, "\\$"); print }' + ##ou: + #quote_arg "`cat`" + ## mais cet dernière version est très lente sur un gros fichier +} +# Dans la chaine $1, remplacer '%' par '%25', '+' par '%2B', '&' par '%26', '=' par '%3D', ' ' par '+' +function quote_formvalue() { + local s="$1" + s="${s//\%/%25}" + s="${s//+/%2B}" + s="${s//&/%26}" + s="${s//=/%3D}" + s="${s// /+}" + echo "$s" +} +# Afficher le nombre de lignes d'un fichier +function get_nblines() { + [ -f "$1" ] && sed -ne '$=' "$1" || echo 0 +} +# générer un fichier temporaire et retourner son nom +function mktempf() { + mktemp "${1:-"$TMPDIR/tmp.XXXXXX"}" +} +# générer un répertoire temporaire et retourner son nom +function mktempd() { + mktemp -d "${1:-"$TMPDIR/tmp.XXXXXX"}" +} +# Créer le répertoire correspondant à un fichier +function mkdirof() { + mkdir -p "$(dirname "$1")" +} +# dans la chaine $1, remplacer \ par \\ et " par \", et l'afficher. ceci est +# utile pour quoter des valeur à insérer dans un script awk +function quote_awk() { + local s + s="${1//\\\\/\\\\}" + s="${s//\"/\\\"}" + echo "$s" +} +# tester si gawk est disponible +function require_gawk() { + local OENC="$UTF8" + if [ -n "$__legacy_awk__" ]; then + ewarn "Cette fonction requière gnuawk" + return 1 + fi + return 0 +} +# lancer awk en essayant d'émuler des fonctionnalités de gnuawk (comme l'option +# -v et la fonction gensub (XXX à faire)) +function uawk() { + if [ -z "$__legacy_awk__" ]; then + awk "$@" + else + # essayer d'émuler l'option -v + local script='BEGIN {' + local done= + while [ -n "$1" ]; do + case "$1" in + --) + shift + done=1 + ;; + + -v) + shift + local name=${1%%=*} + local value=${1:$((${#name} + 1))} + if [ "$value" == "" ]; then + # valeur vide + script="$script +$name = \"\"" + elif [ "${value//[0-9]*/}" == "" ]; then + # valeur numérique + script="$script +$name = $value" + else + # valeur alphanumérique + script="$script +$name = \"$(quote_awk "$value")\"" + fi + ;; + + -*) + #ewarn "option non reconnue: $1" + ;; + + *) + done=1 + ;; + esac + [ -n "$done" ] && break + shift + done + + script="$script +} +$1" + shift + awk "$script" "$@" + fi +} +# Retourner la commande permettant de donner à la variable ${!1} la valeur $2 +function set_var_cmd() { echo "$1=\"$(quote_arg "$2")\""; } +# donner à la variable ${!1} la valeur $2 +function set_var() { eval "$1=\"$(quote_arg "$2")\""; } +# donner à la variable ${!1} la valeur $2. on assume que les valeur sont déjà +# quotées comme il faut +function set_var_literal() { eval "$1=$2"; } +# copier en gardant le maximum de propriétés +function cp_a() { /bin/cp -a "$@"; } +# copier récursivement, en suivant les liens symboliques +function cp_R() { /bin/cp -pR "$@"; } +# supprimer les fichiers dont on donne éventuellement la liste +function rm_maybe() { + local parse_opts=1 arg rm + for arg in "$@"; do + # chercher s'il y a un argument autre que des options + if [ -n "$parse_opts" ]; then + if [ "$arg" == "--" ]; then + parse_opts= + elif [[ "$arg" == "-*" ]]; then + continue + elif [ -n "$arg" ]; then + rm=1 + break + fi + elif [ -n "$arg" ]; then + rm=1 + break + fi + done + [ -n "$rm" ] && /bin/rm "$@" +} +# lister les fichiers ou répertoires du répertoire $1, un par ligne +function list_all() { + # $1=un répertoire dont le contenu doit être listé + # $2..@=un ensemble de patterns pour le listage + local curdir="$(pwd)" + local b="${1:-.}"; shift + + cd "$b" 2>/dev/null || return + eval "/bin/ls -1d ${*:-*} 2>/dev/null" + cd "$curdir" +} +# lister les fichiers du répertoire $1, un par ligne +function list_files() { + # $1=un répertoire dont le contenu doit être listé. + # $2..@=un ensemble de patterns pour le listage + local f + local curdir="$(pwd)" + local b="${1:-.}"; shift + + cd "$b" 2>/dev/null || return + eval "/bin/ls -1d ${*:-*} 2>/dev/null" | while read f; do + [ -f "$f" ] && echo "$f" + done + cd "$curdir" +} +# lister les répertoires du répertoire $1, un par ligne +function list_dirs() { + # $1=un répertoire dont le contenu doit être listé. + # $2..@=un ensemble de patterns pour le listage + local f + local curdir="$(pwd)" + local b="${1:-.}"; shift + + cd "$b" 2>/dev/null || return + eval "/bin/ls -1d ${*:-*} 2>/dev/null" | while read f; do + [ -d "$f" ] && echo "$f" + done + cd "$curdir" +} +# copier un fichier dans un répertoire, ou le contenu d'un répertoire dans un +# autre répertoire, que le répertoire source soit un lien symbolique ou +# non. Cette fonction existe parce que le comportement de "cp_a src dest" n'est +# pas consistant selon les plateformes, surtout si src est un lien symbolique +# sur un répertoire: parfois on copie le lien, parfois on copie le contenu du +# répertoire, parfois on copie le répertoire... +function cpdir() { + # $1=src, $2=dest, $3=method (cp_a par défaut) + local src="$1" dest="$2" method="${3:-cp_a}" + + if [ -d "$src" ]; then + # si c'est un répertoire, traitement particulier + # tout d'abord, s'assurer que la destination existe + if [ ! -d "$dest" ]; then + mkdir -p "$dest" + fi + + # ensuite on fait la copie + local prevdir="$(pwd)" + + dest="$(abspath "$dest")" # XXX abspath défini dans functions! + cd "$src" + if [ -n "$(/bin/ls -a1)" ]; then + # copier les fichiers + [ -n "$(/bin/ls -1)" ] && "$method" * "$dest" + # ne pas oublier les fichiers cachés... + local i + for i in .*; do + [ "$i" == "." -o "$i" == ".." ] && continue + "$method" "$i" "$dest" + done + fi + cd "$prevdir" + else + # sinon, on assume que c'est un fichier + if [ -f "$dest" ]; then + # copie du fichier avec remplacement + "$method" "$src" "$dest" + elif [ -d "$dest" ]; then + # copie du fichier dans le répertoire + "$method" "$src" "$dest" + else + # Copie du fichier avec le nom $dest + mkdirof "$dest" + "$method" "$src" "$dest" + fi + fi +} +# transformer les fins de ligne en LF +function nl2lf() { + if [ -n "$1" -a "$1" != "-" ]; then + tr -d '\r' <"$1" >"$1.tmp.$$" && + /bin/mv "$1.tmp.$$" "$1" + [ -f "$1.tmp.$$" ] && /bin/rm -f "$1.tmp.$$" + else + tr -d '\r' + fi +} +# transformer les fins de ligne en CRLF +function _nl2crlf() { + tr -d '\r' | awk '{print $0 "\r"}' +} +function nl2crlf() { + if [ -n "$1" -a "$1" != "-" ]; then + _nl2crlf <"$1" >"$1.tmp.$$" && + /bin/mv "$1.tmp.$$" "$1" + [ -f "$1.tmp.$$" ] && /bin/rm -f "$1.tmp.$$" + else + _nl2crlf + fi +} +# tester la présence d'un pattern dans un fichier +function quietgrep() { grep -q "$@"; } +# tester si deux fichiers sont identiques +function quietdiff() { diff -q "$@" >&/dev/null; } +# tester si deux fichiers sont identiques/différents +function testsame() { quietdiff "$@"; } +function testdiff() { ! quietdiff "$@"; } +# test si $2 n'existe pas ou si $1 est plus récent que $2 +function testnewer() { test ! -e "$2" -o "$1" -nt "$2"; } +# afficher tous les processus avec le maximum d'informations +function ps_all() { ps -axww; } +# tester l'existence d'un programme dans le PATH +function progexists() { test -n "$1" -a -x "$(which "$1" 2>/dev/null)"; } +# tester si on est root +function is_root() { test `id -u` -eq 0; } +# sourcer un fichier s'il existe +function source_ifexists() { if [ -f "$1" ]; then source "$1" || die; fi; } +# s'endormir pour une très petite période de temps +function little_sleep { LC_NUMERIC=C sleep 0.1; } +# tester si un programme dont on donne le PID tourne +function is_running() { test -n "$(ps -o pid -p "$1" | grep -v PID)"; } +# afficher des chemins "jolis" +function ppath() { + # Dans un chemin *absolu*, remplacer "$HOME" par "~" et "$(pwd)/" par "", + # afin que le chemin soit plus facile à lire. Le répertoire courant est + # spécifié par $2 ou $(pwd) si $2 est vide + local path="$1" cwd="$2" + + # essayer de normaliser le chemin + if [ -d "$path" ]; then path="$(cd "$path"; pwd)" + elif [ -f "$path" ]; then path="$(cd "$(dirname "$path")"; pwd)/$(basename "$path")" + else + : # chemin inexistant, on espère que le chemin est bien formé + fi + + if [ -z "$2" ]; then + cwd="$(pwd)" + fi + [ "$path" = "$cwd" ] && path="." + [ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path/#$cwd\//}" + path="${path/#$HOME/~}" + + echo "$path" +} +# un équivalent de basename écris en bash. note: le cas $1=/ n'est pas traité correctement +function bname() { + local bn="${1%%/}" + bn="${p##*/}" + [ -n "$2" ] && bn="${p%%$2}" + echo "$bn" +} +# un équivalent de dirname écris en bash +function dname() { + local dn="${1%%/}" + case "$dn" in + */*) + dn="${dn%/*}" + ;; + *) + dn="." + ;; + esac + echo "$dn" +} +# sed qui modifie le fichier en place. pour compatibilité avec d'autres +# plateformes (cf system_caps), cette version ne devrait être appelée que de +# cette manière:: +# sedi "script" [files...] +# toute autre utilisation fonctionnerait sur Linux mais pas sur MacOS X ou SunOS +function sedi() { + sed -i "$@" +} +# run_as: fonction pour relancer le script courant afin qu'il tourne avec les +# droits d'un autre user (typiquement root). Attention! cette fonction ne teste +# pas avec si on est déjà ce user. Il y a donc un risque de boucle infinie si on +# ne teste pas le user courant. +function run_as() { + # Lancer cette fonction avec les arguments du script en cours. Par exemple:: + # run_as root "$@" + # Si $2=--noexec, on n'utilise pas la fonction exec, ce qui fait que la + # fonction retourne. Sinon, on peut considérer que cette fonction ne + # retourne jamais + local OENC="$UTF8" + local user="${1:-root}"; shift + local exec_maybe=exec + if [ "$1" = "--noexec" ]; then + exec_maybe= + shift + fi + + # on peut spécifer localement le niveau de verbosité + local VERBOSITY="$VERBOSITY" INTERACTION="$INTERACTION" + set_verbosity "$1" && shift + + estep "Lancement du script sur $HOSTNAME en tant que $user" + if is_yes "$UTOOLS_USES_SU" || ! progexists sudo; then + einfo "Entrez le mot de passe de root" + $exec_maybe su "$user" -c "${BASH:-/bin/sh} \"$0\" $*" + else + einfo "Entrez le mot de passe de $USER (si nécessaire)" + if [ "$user" == "root" ]; then + $exec_maybe sudo "${BASH:-/bin/sh}" "$0" "$@" + else + local args="$*" + args="${args//|/\\|}" + args="${args///\\>}" + args="${args//;/\\;}" + $exec_maybe sudo su "$user" -c "${BASH:-/bin/sh} \"$0\" $args" + fi + fi +} +# run_as_root: fonction pour relancer le script courant afin qu'il tourne en +# root. Attention! cette fonction ne teste pas avec is_root si on est déjà en +# root ou non. Ne pas appeler cette fonction si is_root est vrai, on risque une +# boucle infinie. +function run_as_root() { + run_as root "$@" +} + +# === fonctions pour contrôler l'encoding en sortie et en entrée === +LATIN1=iso-8859-1 +LATIN9=iso-8859-15 +UTF8=utf-8 + +if ! progexists iconv; then + function iconv() { cat; } +fi + +function __lang_encoding() { + local lang="$(<<<"$LANG" awk '{ print tolower($0) }')" + case "$lang" in + fr_fr@euro) echo "iso-8859-15";; + fr_fr) echo "iso-8859-1";; + *.utf-8) echo "utf-8";; + *) ;; + esac +} +function __norm_encoding() { + awk '{ + enc = tolower($0) + gsub(/^latin$/, "latin1", enc) + gsub(/^latin1$/, "iso-8859-1", enc) + gsub(/^latin9$/, "iso-8859-15", enc) + gsub(/[-_]/, "", enc) + if (enc == "iso8859" || enc == "iso88591" || enc == "8859" || enc == "88591") print "iso-8859-1" + else if (enc == "iso885915" || enc == "885915") print "iso-8859-15" + else if (enc == "utf8") print "utf-8" + else print $0 + }' <<<"$1" +} +function __init_encoding() { + local DEFAULT_ENCODING="$(__lang_encoding)" + [ -n "$DEFAULT_ENCODING" ] || DEFAULT_ENCODING=utf-8 + [ -n "$UTOOLS_OUTPUT_ENCODING" ] || UTOOLS_OUTPUT_ENCODING="$DEFAULT_ENCODING" + UTOOLS_OUTPUT_ENCODING="$(__norm_encoding "$UTOOLS_OUTPUT_ENCODING")" + [ -n "$UTOOLS_INPUT_ENCODING" ] || UTOOLS_INPUT_ENCODING="$UTOOLS_OUTPUT_ENCODING" + UTOOLS_INPUT_ENCODING="$(__norm_encoding "$UTOOLS_INPUT_ENCODING")" + [ -n "$UTOOLS_EDITOR_ENCODING" ] || UTOOLS_EDITOR_ENCODING="$UTOOLS_INPUT_ENCODING" + UTOOLS_EDITOR_ENCODING="$(__norm_encoding "$UTOOLS_EDITOR_ENCODING")" + + IENC="$UTOOLS_INPUT_ENCODING" + OENC="$UTOOLS_OUTPUT_ENCODING" +} +__init_encoding + +function tooenc() { + local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + echo "$src" + else + iconv -f "$from" -t "$to" <<<"$src" + fi +} +function uecho() { tooenc "$*"; } +function tooenc_() { + local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + echo_ "$src" + else + echo_ "$src" | iconv -f "$from" -t "$to" + fi +} +function uecho_() { tooenc_ "$*"; } +function toienc() { + local var_="$1" to_="${2:-$IENC}" from_="${3:-$UTOOLS_INPUT_ENCODING}" + if [ "$from_" != "$to_" ]; then + set_var "$var_" "$(iconv -f "$from_" -t "$to_" <<<"${!1}")" + fi +} +function uread() { + local var_ + [ $# == 0 ] && set -- REPLY + read "$@" + for var_ in "$@"; do + [ -z "$var_" -o "${var_:0:1}" == "-" ] && continue # ignorer les options + toienc "$var_" + done +} + +function stooenc() { + local from="${1:-$OENC}" to="${2:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + cat + else + iconv -f "$from" -t "$to" + fi +} +function stoolatin1() { stooenc "iso-8859-1"; } +function stoolatin9() { stooenc "iso-8859-15"; } +function stooutf8() { stooenc "utf-8"; } +function stoienc() { + local to="${1:-$IENC}" from="${2:-$UTOOLS_INPUT_ENCODING}" + if [ "$from" == "$to" ]; then + cat + else + iconv -f "$from" -t "$to" + fi +} +function stoilatin1() { stoienc "iso-8859-1"; } +function stoilatin9() { stoienc "iso-8859-15"; } +function stoiutf8() { stoienc "utf-8"; } + +# === fonctions pour contrôler l'affichage et l'interaction utilisateur === +# ces fonctions n'affiche le message que si le niveau de "verbosité" est +# suffisant: +# 4=afficher les messages de debug; 3=afficher les message informatifs; +# 2=afficher les warnings et les notes; 1=afficher les erreurs +# 0=ne rien afficher + +# niveau de "verbosité". on commence par défaut à 3 +MIN_VERBOSITY=0 +DEF_VERBOSITY=3 +MAX_VERBOSITY=4 +export VERBOSITY="${VERBOSITY:-$DEF_VERBOSITY}" +# niveau d'interaction. +MIN_INTERACTION=0 +DEF_INTERACTION=2 +MAX_INTERACTION=3 +export INTERACTION="${INTERACTION:-$DEF_INTERACTION}" + +function set_verbosity() { + # Configurer le niveau de verbosité ou d'interaction en fonction de la + # valeur de $1. Retourner 0 si l'argument a été utilisé, 1 sinon + + # $2 est la valeur d'un préfixe pour reconnaitre des options courtes + # supplémentaires. Par défaut, les options suivantes sont reconnues: -c, -q, + # -v, -Q, -D, ... Si le préfixe est -v par exemple, les options reconnues + # seront -vc, -vq, -vv, -vQ, -vD, ... *en plus* de -c, -q, -v, -Q, -D, ... + + local retval=0 + local enable_fake_arg= + local prefix="-" + + if [ "$1" = "--enable-fake-arg" ]; then + shift + enable_fake_arg=1 + fi + + [ -n "$2" ] && prefix="$2" + case "$1" in + # Ne changer, ni le niveau de verbosité, ni le niveau d'interaction, mais + # seulement si --enable-fake-arg a été activé + # Cette option permet à ask_yesno() et read_value() de spécifier les + # messages qui doivent être affichés dans le niveau par défaut de + # verbosité/interaction + -c|"${prefix}c"|--default) + [ -n "$enable_fake_arg" ] || retval=1 + ;; + + # Configurer le niveau de verbosité en fonction de l'argument: -q le + # diminue, -Q le met au minimum, -v l'augmente, -D le met au maximum + -q|"${prefix}q"|--quiet) + [ $VERBOSITY -gt $MIN_VERBOSITY ] && VERBOSITY=$(($VERBOSITY - 1)) + ;; + -v|"${prefix}v"|--verbose) + [ $VERBOSITY -lt $MAX_VERBOSITY ] && VERBOSITY=$(($VERBOSITY + 1)) + ;; + -Q|"${prefix}Q"|--very-quiet) + VERBOSITY=$MIN_VERBOSITY + ;; + -D|"${prefix}D"|--debug) + VERBOSITY=$MAX_VERBOSITY + ;; + + # Configurer le niveau d'interaction en fonction de l'argument: -b le met au + # minimum, -y le diminue, -i l'augmente + -b|"${prefix}b"|--batch) + # niveau d'interaction minimum + INTERACTION=$MIN_INTERACTION + ;; + -y|"${prefix}y"|--automatic) + # Dimininuer le niveau d'interaction + [ $INTERACTION -gt $MIN_INTERACTION ] && INTERACTION=$(($INTERACTION - 1)) + ;; + -i|"${prefix}i"|--interactive) + # Augmenter le niveau d'interaction. Augmenter aussi le niveau de verbosité + [ $INTERACTION -lt $MAX_INTERACTION ] && INTERACTION=$(($INTERACTION + 1)) + ;; + + *) + retval=1 + ;; + esac + return $retval +} + +function check_verbosity() { + # Teste si le niveau de verbosité/interaction courant est suffisant pour une + # opération dont le niveau est donné par $1 + + # Par exemple, 'check_verbosity -y' ne retourne vrai que si le niveau + # d'interaction courant est supérieur ou égal à ($DEF_INTERACTION - 1). Cela + # permet d'afficher un message ou d'effectuer une opération si on est au + # moins en mode automatic (par contre, en mode batch, l'opération ne sera + # pas effectué + + # De même 'check_verbosity -v' ne retourne vrai que si le niveau de + # verbosité courant est supérieur ou égal à ($DEF_VERBOSITY+1). Cela permet + # de n'afficher un message que si l'on est en mode verbose par exemple. + + # 'check_verbosity -c' retourne toujours vrai + + # Si $1 n'est pas un argument valide pour set_verbosity, alors on teste le + # niveau de verbosité ou d'interaction par défaut. si $2!="", alors on teste + # si le niveau de verbosité courant est supérieur à $2. Si $3!="", alors on + # teste sur le niveau d'interaction est supérieur à $3 + + # Si le premier argument est --action, alors on affiche "shift", "return", + # ou ":" suivant l'action à effectuer. Sinon, retourner 0 si le niveau est + # correct (Si $2="" et $3="", l'argument a été interprété et il faut faire + # un shift. Sinon l'argument a peut-être été interprété. On ne sait pas + # alors, sauf si on spécifie --action, s'il faut faire un shift ou non), 1 + # si le niveau n'est pas correct, mais l'argument $1 a été interprété (donc + # il faut faire un shift) et 2 si l'argument n'est pas correct (n'a pas été + # interprété) + + local verbosity="$VERBOSITY" interaction="$INTERACTION" + local VERBOSITY="$DEF_VERBOSITY" INTERACTION="$DEF_INTERACTION" + local action= + + if [ "$1" = "--action" ]; then + shift + action=1 + fi + + if set_verbosity --enable-fake-arg "$1"; then + shift + if [ $interaction -ne $INTERACTION -a $interaction -ge $INTERACTION ] || + [ $verbosity -ne $VERBOSITY -a $verbosity -ge $VERBOSITY ] || + [ $interaction -eq $INTERACTION -a $verbosity -eq $VERBOSITY ]; then + [ -n "$action" ] && echo shift + return 0 + else + [ -n "$action" ] && echo return + return 1 + fi + else + if [ -n "$2" ] && [ "$verbosity" -gt "$2" ]; then + [ -n "$action" ] && echo : + return 0 # ce cas n'est pas équivalent à celui ci-dessus (pas de shift à faire!) + elif [ -n "$3" ] && [ "$interaction" -gt "$3" ]; then + [ -n "$action" ] && echo : + return 0 # ce cas n'est pas équivalent à celui ci-dessus (pas de shift à faire!) + else + [ -n "$action" ] && echo return + return 2 + fi + fi +} + +function echo_() { echo -n "$*"; } + +# Couleurs +tty -s <&1 && NO_COLORS= || NO_COLORS=1 +function get_color() { + [ -n "$NO_COLORS" ] && return + [ -z "$*" ] && set RESET + + echo_ $'\e[' + local sep + while [ -n "$1" ]; do + [ -n "$sep" ] && echo_ ";" + case "$1" in + z|RESET) echo_ "0";; + o|BLACK) echo_ "30";; + r|RED) echo_ "31";; + g|GREEN) echo_ "32";; + y|YELLOW) echo_ "33";; + b|BLUE) echo_ "34";; + m|MAGENTA) echo_ "35";; + c|CYAN) echo_ "36";; + w|WHITE) echo_ "37";; + DEFAULT) echo_ "39";; + O|BLACK_BG) echo_ "40";; + R|RED_BG) echo_ "41";; + G|GREEN_BG) echo_ "42";; + Y|YELLOW_BG) echo_ "43";; + B|BLUE_BG) echo_ "44";; + M|MAGENTA_BG) echo_ "45";; + C|CYAN_BG) echo_ "46";; + W|WHITE_BG) echo_ "47";; + DEFAULT_BG) echo_ "49";; + @|BOLD) echo_ "1";; + -|FAINT) echo_ "2";; + _|UNDERLINED) echo_ "4";; + ~|REVERSE) echo_ "7";; + n|NORMAL) echo_ "22";; + esac + sep=1 + shift + done + echo_ "m" +} + +COULEUR_ROUGE="$(get_color RED BOLD)" +COULEUR_VERTE="$(get_color GREEN BOLD)" +COULEUR_JAUNE="$(get_color YELLOW BOLD)" +COULEUR_BLEUE="$(get_color BLUE BOLD)" +COULEUR_BLANCHE="$(get_color WHITE BOLD)" +COULEUR_NORMALE="$(get_color RESET)" + +# tester le niveau de "verbosité" ou d'interaction +function is_quiet() { [ $VERBOSITY -lt $DEF_VERBOSITY ]; } +function is_verbose() { ! is_quiet; } +function is_interactive() { [ $INTERACTION -ge $DEF_INTERACTION ]; } +function is_automatic() { [ $INTERACTION -lt $DEF_INTERACTION ]; } +function is_batch() { [ $INTERACTION -eq $MIN_INTERACTION ]; } + +# afficher une erreur *sur stderr* +function display_error() { [ $VERBOSITY -gt 0 ]; } +function eerror() { + eval `check_verbosity --action "$1" 0` + echo_ "${COULEUR_ROUGE}error:${COULEUR_NORMALE} " 1>&2 + tooenc "$*" 1>&2 +} + +# afficher un warning ou une note *sur stderr* +function display_warning() { [ $VERBOSITY -gt 1 ]; } +function ewarn() { + eval `check_verbosity --action "$1" 1` + echo_ "${COULEUR_JAUNE}warning:${COULEUR_NORMALE} " 1>&2 + tooenc "$*" 1>&2 +} +function enote() { + eval `check_verbosity --action "$1" 1` + echo_ "${COULEUR_VERTE}note:${COULEUR_NORMALE} " 1>&2 + tooenc "$*" 1>&2 +} + +# afficher une information +function display_info() { [ $VERBOSITY -gt 2 ]; } +function etitle() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}>>> " + tooenc_ "$*" + echo " <<<${COULEUR_NORMALE}" +} +function esubtitle() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}*** " + tooenc_ "$*" + echo "${COULEUR_NORMALE}" +} +function eimportant() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_ROUGE}important:${COULEUR_NORMALE} " + tooenc "$*" +} +function eattention() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_JAUNE}attention:${COULEUR_NORMALE} " + tooenc "$*" +} +function ecomment() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_VERTE}commentaire:${COULEUR_NORMALE} " + tooenc "$*" +} +function einfo() { + eval `check_verbosity --action "$1" 2` + tooenc "$*" +} +function einfon() { + eval `check_verbosity --action "$1" 2` + tooenc_ "$*" +} + +# afficher un message de debug +function is_debug() { [ $VERBOSITY -gt 3 ]; } +function edebug() { + eval `check_verbosity --action "$1" 3` + echo_ "${COULEUR_BLANCHE}debug:${COULEUR_NORMALE} " + tooenc "$*" +} +function edebugn() { + eval `check_verbosity --action "$1" 3` + echo_ "${COULEUR_BLANCHE}debug:${COULEUR_NORMALE} " + tooenc_ "$*" +} + +# Fonctions d'affichage pour suivre la progression d'opérations +function estep() { + # Une opération entière + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}*${COULEUR_NORMALE} " + tooenc "$*" +} +function estepn() { + # Une opération entière + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}*${COULEUR_NORMALE} " + tooenc_ "$*" +} + +function ebegin() { + # Le début d'une opération + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}*${COULEUR_NORMALE} " + tooenc_ "$*" + echo_ ":" + utools_ESTATUS_=0 + utools_edot_first_=1 +} + +function edot() { + # Une étape d'une opération commencée par ebegin, matérialisée par un "." + # Si l'argument est un fichier, utiliser -f pour que "$HOME" et "$(pwd)/" + # soient remplacés respectivement par "~" et "" (afin que les chemins soient + # plus courts) + # Si l'argument est un commentaire, utiliser -m pour le spécifier. -w + # indique que l'argument est un warning: un x jaune est affiché, avec le + # message associé si on est en mode debug. + # Sinon, l'argument est un status. 0 affiche un ".", sinon on affiche un "x" + local status=$? + local warn= color= + + if [ -n "$utools_edot_first_" ]; then + # au premier dot, afficher un espace d'abord + echo_ " " + utools_edot_first_= + fi + + eval `check_verbosity --action "$1" 2` + if is_debug; then + local msg paths path + if [ "$1" = "-f" ]; then + # chemins + shift + for path in "$@"; do + paths="${paths:+$paths,}$(ppath "$path")" + done + msg="$paths" + elif [ "$1" = "-m" -o "$1" = "-w" ]; then + # message + [ "$1" = "-w" ] && warn=1 # message de warning + shift + msg="$(tooenc "$*")" + else + # status + [ -n "$1" ] && status="$1" + fi + + [ "$utools_ESTATUS_" -eq 0 -a "$status" -ne 0 ] && utools_ESTATUS_="$status" + if [ "$status" -eq 0 -a -z "$warn" ]; then + echo_ "${msg:+ + }.${msg:+($msg)}" + else + color="$COULEUR_ROUGE" + [ -n "$warn" ] && color="$COULEUR_JAUNE" + echo_ "${msg:+ + }${color}x${COULEUR_NORMALE}${msg:+($msg)}" + fi + else + if [ "$1" = "-f" -o "$1" = "-m" -o "$1" = "-w" ]; then + [ "$1" = "-w" ] && warn=1 # message de warning + shift + else + [ -n "$1" ] && status="$1" + fi + + [ "$utools_ESTATUS_" -eq 0 -a "$status" -ne 0 ] && utools_ESTATUS_="$status" + if [ "$status" -eq 0 -a -z "$warn" ]; then + echo_ "." + else + color="$COULEUR_ROUGE" + [ -n "$warn" ] && color="$COULEUR_JAUNE" + echo_ "${color}x${COULEUR_NORMALE}" + fi + fi +} + +function ewait() { + # Une étape d'un opération commencée par ebegin, matérialisée par des "+" + # qui s'affichent tant que le processus de PID $1 tourne + # à utiliser de cette manière: + # ebegin "lancement de cmd" + # cmd & + # ewait $! + # eend + local action status + action="$(check_verbosity --action "$1" 2)" + status=$? # le status de check_verbosity + if [ "$action" == "return" ]; then # + # ne rien afficher, mais attendre quand même que le processus s'arrête + [ $status -eq 1 ] && shift + + local pid="$1" + [ -n "$pid" ] && wait "$pid" + return + fi + + eval "$action" + + if [ -n "$utools_edot_first_" ]; then + # au premier dot, afficher un espace d'abord + echo_ " " + utools_edot_first_= + fi + + local pid="$1" + local count=2 # attendre 2 secondes avant de commencer à afficher des "+" + [ -z "$pid" ] && return + + little_sleep # certains processus retournent tout de suite + while is_running "$pid"; do + sleep 1 + if [ $count -gt 0 ]; then + count=$(($count - 1)) + else + echo_ "+" + fi + done + echo_ "." # terminer par un "." +} + +function eend() { + # la fin d'une opération commencée par ebegin + local status=0 + + eval `check_verbosity --action "$1" 2` + + [ -n "$1" ] && status="$1" + [ "$utools_ESTATUS_" -eq 0 -a "$status" -ne 0 ] && utools_ESTATUS_="$status" + if [ "$utools_ESTATUS_" -eq 0 ]; then + echo " ${COULEUR_VERTE}ok${COULEUR_NORMALE}" + else + echo " ${COULEUR_ROUGE}error${COULEUR_NORMALE}" + fi + + status=$utools_ESTATUS_ + unset utools_ESTATUS_ + return $status +} + +# Arrêter le script avec un message d'erreur +function die() { [ -n "$*" ] && eerror "$@"; exit 1; } + +# === Nom du système === +export SYSTEM_NAME="`uname -s`" +beginswith "$SYSTEM_NAME" CYGWIN && SYSTEM_NAME=Cygwin +beginswith "$SYSTEM_NAME" MINGW32 && SYSTEM_NAME=Mingw +export SYSTEM_MACHINE="`uname -m`" + +if [ "$SYSTEM_NAME" == "Linux" ]; then + if [ -f /etc/debian_version ]; then + LINUX_FLAVOUR=debian + elif [ -f /etc/gentoo-release ]; then + LINUX_FLAVOUR=gentoo + elif [ -f /etc/redhat-release ]; then + LINUX_FLAVOUR=redhat + else + LINUX_FLAVOUR= + fi + if [ "$SYSTEM_MACHINE" == "x86_64" ]; then + SYSTEM_BITS=64 + else + SYSTEM_BITS=32 + fi +elif [ "$SYSTEM_NAME" == "Darwin" ]; then + MACOSX_VERSION="$(grep -A 1 CFBundleShortVersionString /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist | grep string | sed 's/.*//g +s/<\/string>.*$//g')" + MACOSX_NAME=UNSUPPORTED + if beginswith "$MACOSX_VERSION" 10.5; then + MACOSX_NAME=LEOPARD + elif beginswith "$MACOSX_VERSION" 10.4; then + MACOSX_NAME=TIGER + fi + SYSTEM_BITS= +fi + +# === vérifications === +[ -d "$TMPDIR" ] || ewarn "TMPDIR ($TMPDIR) n'existe pas" diff --git a/legacy/sysinc/basevars b/legacy/sysinc/basevars new file mode 100644 index 0000000..c19caca --- /dev/null +++ b/legacy/sysinc/basevars @@ -0,0 +1,19 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Definitions de variables diverses. Il n'est pas necessaire d'inclure ce +# fichier si vous utilisez deja sysinc/base. Ce fichier est pour les scripts qui +# ont simplement besoin des definitions, pas des fonctions. + +# repertoire temporaire +export TMPDIR="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}" + +# Nom du systeme +export SYSTEM_NAME="`uname -s`" +[ "${SYSTEM_NAME#CYGWIN}" != "$SYSTEM_NAME" ] && SYSTEM_NAME=Cygwin +[ "${SYSTEM_NAME#MINGW32}" != "$SYSTEM_NAME" ] && SYSTEM_NAME=Mingw + +# emplacement du script courant +scriptname="`basename \"$0\"`" +scriptdir="`dirname \"$0\"`" +scriptdir="`cd \"$scriptdir\"; pwd`" +script="$scriptdir/$scriptname" diff --git a/legacy/sysinc/begingetopt b/legacy/sysinc/begingetopt new file mode 100644 index 0000000..9626a7e --- /dev/null +++ b/legacy/sysinc/begingetopt @@ -0,0 +1,3 @@ +eoo= +while [ -n "$1" ]; do + case "$1" in diff --git a/legacy/sysinc/begingetopt-- b/legacy/sysinc/begingetopt-- new file mode 100644 index 0000000..a3d54d0 --- /dev/null +++ b/legacy/sysinc/begingetopt-- @@ -0,0 +1,4 @@ +eoo= +first_option=1 +while [ -n "$1" ]; do + case "$1" in diff --git a/legacy/sysinc/begingetopt--_help b/legacy/sysinc/begingetopt--_help new file mode 100644 index 0000000..2b9404a --- /dev/null +++ b/legacy/sysinc/begingetopt--_help @@ -0,0 +1,8 @@ +eoo= +first_option=1 +while [ -n "$1" ]; do + case "$1" in + -h|--help) + display_help + exit 0 + ;; diff --git a/legacy/sysinc/begingetopt_help b/legacy/sysinc/begingetopt_help new file mode 100644 index 0000000..82663e1 --- /dev/null +++ b/legacy/sysinc/begingetopt_help @@ -0,0 +1,7 @@ +eoo= +while [ -n "$1" ]; do + case "$1" in + -h|--help) + display_help + exit 0 + ;; diff --git a/legacy/sysinc/crontab b/legacy/sysinc/crontab new file mode 100644 index 0000000..ba1a0a4 --- /dev/null +++ b/legacy/sysinc/crontab @@ -0,0 +1,36 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require sysinc/base +##@require sysinc/functions + +function add_to_crontab() { + # Ajouter la ligne $1 au crontab de l'utilisateur $2 + local -a crontab=(crontab ${2:+-u "$2"}) + local current="$("${crontab[@]}" -l 2>/dev/null)" + local tmpfile + ac_set_tmpfile tmpfile + if [ -n "$current" ]; then + echo "$current" >"$tmpfile" + fi + if ! quietgrep -F "$1" "$tmpfile"; then + echo "$1" >>"$tmpfile" + "${crontab[@]}" "$tmpfile" + return 0 + fi + return 1 +} + +function remove_from_crontab() { + # Supprimer la ligne $1 du crontab de l'utilisateur $2 + local -a crontab=(crontab ${2:+-u "$2"}) + local current="$("${crontab[@]}" -l 2>/dev/null)" + local tmpfile + ac_set_tmpfile tmpfile + if [ -n "$current" ]; then + echo "$current" >"$tmpfile" + fi + if quietgrep -F "$1" "$tmpfile"; then + grep -vF "$1" "$tmpfile" | "${crontab[@]}" - + return 0 + fi + return 1 +} diff --git a/legacy/sysinc/endgetopt b/legacy/sysinc/endgetopt new file mode 100644 index 0000000..955e9c6 --- /dev/null +++ b/legacy/sysinc/endgetopt @@ -0,0 +1,18 @@ + --) + shift + eoo=1 + ;; + + -*) + if ! set_verbosity "$1" -v; then + ewarn "option non reconnue: $1" + fi + ;; + + *) + eoo=1 + ;; + esac + [ -n "$eoo" ] && break + shift +done diff --git a/legacy/sysinc/endgetopt-- b/legacy/sysinc/endgetopt-- new file mode 100644 index 0000000..8fe06a6 --- /dev/null +++ b/legacy/sysinc/endgetopt-- @@ -0,0 +1,23 @@ + --) + if [ -n "$USE_DASHDASH" ]; then + [ -z "$first_option" ] && shift + else + shift + fi + eoo=1 + ;; + + -*) + if ! set_verbosity "$1" -v; then + ewarn "option non reconnue: $1" + fi + ;; + + *) + eoo=1 + ;; + esac + first_option= + [ -n "$eoo" ] && break + shift +done diff --git a/legacy/sysinc/ewaitcmd.test b/legacy/sysinc/ewaitcmd.test new file mode 100644 index 0000000..557e725 --- /dev/null +++ b/legacy/sysinc/ewaitcmd.test @@ -0,0 +1,63 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# ewaitcmd devait remplacer ewait, mais l'implémentation souffre de problèmes de +# synchronisation entre l'affichage et l'exécution de la commande. Le code est +# stocké dans ce fichier, en attendant de trouver une solution... + +function __utools_eplus_file() { + # Afficher le nom d'un fichier à utiliser pour __utools_eplus() + local file="$TMPDIR/utools.$$.${utools_eplus_count_:=0}" + while [ -f "$file" ]; do + utools_eplus_count_=$(($utools_eplus_count_ + 1)) + file="$TMPDIR/utools.$$.$utools_eplus_count_" + done + >"$file" + echo "$file" +} + +function __utools_eplus() { + # Après un délai de 2 secondes, afficher un caractère "+" toutes les + # secondes, et ce tant que le fichier $1 existe + [ -f "$1" ] || return + sleep 2 + + while true; do + [ -f "$1" ] || break + echo_ "+" + sleep 1 + done +} + +function ewaitcmd() { + # Une étape d'une opération commencée par ebegin, matérialisée par des "+" + # qui s'affichent tant que le processus correspondant à la ligne de commande + # tourne. + # XXX problèmes: problèmes de synchronisme... + local eplus_file exitcode + + if [ -n "$utools_edot_first_" ]; then + # au premier dot, afficher un espace d'abord + echo_ " " + utools_edot_first_= + fi + + # commencer à afficher les "+" + eplus_file="$(__utools_eplus_file)" + __utools_eplus "$eplus_file" & + + # lancer la commande + "$@" + exitcode=$? + + # arrêter l'affichage des "+" + /bin/rm -f "$eplus_file" + + if [ $exitcode -ne 0 ]; then + [ "$utools_ESTATUS_" -eq 0 ] && utools_ESTATUS_=$exitcode + echo_ "${COULEUR_ROUGE}x${COULEUR_NORMALE}" + else + echo_ "." + fi + + return $exitcode +} diff --git a/legacy/sysinc/functions b/legacy/sysinc/functions new file mode 100644 index 0000000..097f517 --- /dev/null +++ b/legacy/sysinc/functions @@ -0,0 +1,1826 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +################################################## +# Gestion des fichiers + +function filter_conf() { + # filtrer un fichier de configuration en enlevant les commentaires et les lignes vides + # $1==-m fusionner les lignes qui se terminent par \ + local merge=cat + if [ "$1" == -m ]; then + merge='awk '\''substr($0, length($0)) == "\\" { while (substr($0, length($0)) == "\\") { getline nextline; $0 = substr($0, 1, length($0) - 1) nextline }; print; next } {print}'\' + fi + grep -v '^#' | grep -v '^$' | eval "$merge" +} + +################################################## +# Gestion des fichiers et répertoires temporaires +function make_tempfile() { + # $1=template du fichier à créer + local mktmpdir= + if [ "$1" == "-d" ]; then + shift + mktmpdir=1 + fi + local template="${1:-$TMPDIR/tmp.XXXXXX}" + local base="${template%%X*}" + local XXXs="${template#$base}" + [ -z "$XXXs" ] && XXXs="XXXXXX" + local nbX="${#XXXs}" + + local n="$$0" suff + while true; do + suff="$n" + while [ ${#suff} -lt $nbX ]; do suff="0$suff"; done + + tempfile="$base$suff" + if [ -n "$mktmpdir" ]; then + # répertoire temporaire + if [ ! -d "$tempfile" ]; then + mkdir "$tempfile" + break + fi + else + # fichier temporaire + if [ ! -f "$tempfile" ]; then + touch "$tempfile" + break + fi + fi + + n=$(($n + 1)) + done + + echo "$tempfile" +} + +# autoclean: gérer une liste de fichiers temporaires à supprimer en fin de programme +__ac_files=() +function __ac_trap() { + local file_ + for file_ in "${__ac_files[@]}"; do + [ -e "$file_" ] && rm -rf "$file_" + done +} +trap __ac_trap 1 3 15 EXIT +function autoclean() { + # Ajouter $1..$n à la liste des fichiers à supprimer à la fin du programme + local file_ + for file_ in "$@"; do + [ -n "$file_" ] && array_add __ac_files "$file_" + done +} +function ac_set_tmpfile() { + # Créer un fichier temporaire avec le motif $2, l'ajouter à la liste des + # fichiers à supprimer en fin de programme, et mettre sa valeur dans la + # variable $1 + local tmpfile_="$(mktempf "$2")" + autoclean "$tmpfile_" + set_var "$1" "$tmpfile_" +} +function ac_set_tmpdir() { + # Créer un répertoire temporaire avec le motif $2, l'ajouter à la liste des + # fichiers à supprimer en fin de programme, et mettre sa valeur dans la + # variable $1 + local tmpdir_="$(mktempd "$2")" + autoclean "$tmpdir_" + set_var "$1" "$tmpdir_" +} + +################################################## +# args: gestion d'un ensemble d'arguments + +function read_args() { + # Lire les arguments et les mettre sur plusieurs lignes dans la variable + # dont le nom est donné dans $1. Ceci permet de façon fiable de traiter des + # arguments avec des espaces dedans. + + # note: cela suppose bien sûr que les arguments ne contiennent pas de retour + # à la ligne!!! + if [ -z "$1" ]; then return 1; fi + + local varname_="$1"; shift + local args_ arg_ + + for arg_ in "$@"; do + args_="${args_:+$args_ +}$arg_" + done + set_var "$varname_" "$args_" +} + +function next_arg() { + # Soit la variable args dont le nom est donné dans $1, et la variable next + # dont le nom est donné dans $2. Mettre à jour next avec la première ligne + # de la variable args. Mettre à jour args avec le reste des lignes. + if [ -z "$1" -o -z "$2" ]; then return 1; fi + + local args_="$1" + local arg_="$2" + local line_ first_=1 first_line_ last_lines_ + + eval "$((echo "${!args_}"; echo "------") | while read line_; do + if [ "$line_" == "------" ]; then + echo "first_line_=\"$(quote_arg "$first_line_")\" +last_lines_=\"$(echo "$last_lines_" | awk '/.+/ { print; next }' | quote_string)\"" + elif [ -n "$first_" ]; then + first_= + first_line_="$line_" + else + last_lines_="$last_lines_ +$line_" + fi + done)" + set_var "$args_" "$last_lines_" + set_var "$arg_" "$first_line_" + if [ -z "$first_line_" ]; then return 1; fi +} + +function get_option() { + # $1 est un argument de la forme "option:value" + # $2 est une liste d'options valides, séparées par des espaces + # $3 est un suffixe à rajouter au nom de variable + + # Si $1 est une option valide (de la forme 'option:value' avec option + # faisant partie de la liste (optionnelle) des options valides) alors mettre + # à jour la variable $option avec la valeur value. sinon, retourner un code + # d'erreur + + local arg_="$1" + local valid_options_="$2" valid_option_ is_valid_ option_ value_ + local suffix_="$3" + + option_="$(expr "$arg_" : "\(..*\):")" + [ -z "$option_" ] && return 1 + + if [ -n "$2" ]; then + # vérifier que $option est valide + is_valid_= + for valid_option_ in $valid_options_; do + if [ "$option_" == "$valid_option_" ]; then + is_valid_=1 + break + fi + done + [ -z "$is_valid_" ] && return 1 + fi + + value_="${1##$option_:}" + set_var "$option_$suffix_" "$value_" +} + +################################################## +# input: fonctions pour poser des question et saisir des valeurs + +function ask_yesno() { + # $1=message, $2=default (N par défaut) + # Afficher le message $message suivi de [oN] ou [On] suivant que $default + # vaut O ou N. Retourner 0 si la réponse est oui, 1 sinon + + # Si default vaut C, alors la valeur par défaut dépend du niveau + # d'interactivité: N si on est interactif et O si on est pas interactif + + # Support de l'affichage de la question suivant le niveau d'interaction + # et/ou de verbosité Le message ne sera affiché que si le niveau + # d'interaction (ou de verbosité) courant est supérieur ou égal à celui qui + # est spécifié en argument. Sinon, la valeur par défaut est retournée + local r + check_verbosity "$1"; r=$?; [ $r -le 1 ] && shift + + local message="$1" + local prompt="[oN]" + local default="${2:-N}" + if [ "$default" == "C" ]; then + if [ $r -ne 1 ]; then + default=N + else + default=O + fi + fi + is_yes "$default" && prompt="[On]" + + if [ $r -ne 1 ]; then + if [ -n "$message" ]; then + tooenc_ "$message" + else + tooenc_ "Voulez-vous continuer?" "$UTF8" + fi + echo_ " $prompt " + uread r + else + r= + fi + + is_yes "${r:-$default}" +} + +function read_value() { + # $1=message, $2=varname, $3=default, $4=refuse_empty + # Afficher le message $message suivi la valeur par défaut [$default] si elle + # est non vide, puis lire la valeur donnée par l'utilisateur. Cette valeur + # doit être non vide si $refuse_empty est vrai (ce qui est le cas par + # défaut) + # La valeur saisie est placée dans la variable $varname (par défaut value) + + # note: les variables locales sont suffixées de _ pour éviter que $varname + # soit égal à une des variables locales. + + # Support l'affichage de la question suivant le niveau de "verbosité" + # Le message ne sera affiché que si le niveau de verbosité courant est + # supérieur ou égal à celui qui est spécifié en argument. Sinon, la valeur + # par défaut est retournée. (note: si l'utilisateur requière que la valeur + # soit non vide et que la valeur par défaut est vide, le script s'arrête + # avec une erreur) + local r_ + check_verbosity "$1"; r_=$?; [ $r_ -le 1 ] && shift + + local message_="$1" + local varname_="${2:-value}" + local default_="$3" + local refuse_empty_="${4:-O}" + + local read_value_= + if [ $r_ -ne 1 ]; then + read_value_=1 + elif [ -z "$default_" ] && is_yes "$refuse_empty_"; then + read_value_=1 + fi + + if [ -n "$read_value_" ]; then + while true; do + if [ -n "$message_" ]; then + tooenc_ "$message_" + else + tooenc_ "Entrez la valeur" "$UTF8" + fi + [ -n "$default_" ] && tooenc_ " [$default_]" + echo_ ": " + uread r_; r_="${r_:-$default_}" + if [ -n "$r_" ] || ! is_yes "$refuse_empty_"; then + set_var "$varname_" "$r_" + return 0 + fi + done + else + if [ -z "$default_" ] && is_yes "$refuse_empty_"; then + OENC="$UTF8" die "La valeur par défaut de $varname_ doit être non vide" + else + set_var "$varname_" "$default_" + fi + fi +} + +################################################## +# menu: fonctions afficher un menu + +function simple_menu() { + # $1=option_varname + # $2=options (une par ligne) + # $3..*=votre_choix:, titre_du_menu:, choix_par_defaut: + + # Afficher un menu dont les membres sont les lignes de $options. Un choix + # doit être saisi sous la forme num_option. L'option choisie est placée dans + # la variable $option_varname + + # note: les variables locales sont suffixées de _ pour éviter que + # $option_varname soit égal à une des variables locales. + + local tab_=" " # contient une tabulation + local i_ c_ + local options_one_by_line_ options_ option_ + local choice_ choice_option_ + local option_varname_ + local votre_choix_ titre_du_menu_ choix_par_defaut_ + + # nom de la variable destination + option_varname_="$1"; shift + if [ -z "$option_varname_" ]; then + OENC="$UTF8" eerror "option_varname doit être spécifié" + return 1 + fi + choix_par_defaut_="${!option_varname_}" + + # Construire le tableau des options + options_one_by_line_="$1"; shift + if [ -z "$options_one_by_line_" ]; then + OENC="$UTF8" eerror "options doit être non vide" + return 1 + fi + i_=0 + while next_arg options_one_by_line_ option_; do + options_[$i_]="$option_" + i_=$(($i_ + 1)) + done + + # autres paramètres + while [ -n "$1" ]; do + ! get_option "$1" "votre_choix titre_du_menu choix_par_defaut" _ && break + shift + done + + c_=0 + while true; do + if [ "$c_" == "0" ]; then + # Afficher le menu + [ -n "$titre_du_menu_" ] && tooenc "=== $titre_du_menu_ ===" + i_=1 + for option_ in "${options_[@]}"; do + if [ "$option_" == "$choix_par_defaut_" ]; then + echo "$i_*- $option_" + else + echo "$i_ - $option_" + fi + i_=$(($i_ + 1)) + done + fi + + # Afficher les choix + if [ -n "$votre_choix_" ]; then + tooenc_ "$votre_choix_" + else + tooenc_ "Entrez le numéro de l'option choisie" "$UTF8" + fi + echo_ ": " + uread choice_ + + # valeur par défaut + if [ -z "$choice_" -a -n "$choix_par_defaut_" ]; then + choice_option_="$choix_par_defaut_" + break + fi + # vérifier la saisie + choice_option_="$(expr "$choice_" : "[ $tab_]*\([0-9]*\)")" + if [ -n "$choice_option_" ] && [ "$choice_option_" -gt "0" -a "$choice_option_" -le "${#options_[*]}" ]; then + # numéro d'option correct + choice_option_="${options_[$(($choice_option_ - 1))]}" + break + else + OENC="$UTF8" eerror "Numéro d'option incorrect" + fi + + c_=$(($c_ + 1)) + if [ "$c_" == "5" ]; then + echo "" # sauter une ligne toutes les 4 tentatives + c_=0 + fi + done + + set_var "$option_varname_" "$choice_option_" +} + +function actions_menu() { + # $1=action_varname, $2=option_varname + # $3=actions (séparées par des espaces), $4=options (une par ligne) + # $5..*=action_quitter:, action_par_defaut:, actions_vides:, votre_choix:, + # titre_du_menu:, message_sans_options: + + # Afficher un menu dont les membres sont les lignes de $options. Sur + # chacunes de ces entrées de menu, les actions de $actions peuvent être + # appliquées. Un choix doit être saisi sous la forme [action]num_option. Si + # action n'est pas précisé, il s'agit de l'action par défaut qui est + # sélectionnée. Par défaut, action_par_defaut est la première action, et + # action_quitter (qui provoque la sortie du menu sans choix) est la dernière + # action + + # actions est une chaine d'éléments de la forme "k:Description" séparés par + # des espaces, où k est un lettre, et Description une description de + # l'action. Dans la chaine Description, les caractères '_' sont remplacés + # par des espaces pour l'affichage + + # l'action choisie est placée dans la variable $action_varname. l'option + # choisie est placée dans la variable $option_varname + + # note: les variables locales sont suffixées de _ pour éviter que + # $action_varname ou $option_varname soient égals à une des variables + # locales. + + local tab_=" " # contient une tabulation + local i_ c_ error_displayed_ + local options_one_by_line_ options_ option_ + local actions_sep_by_space_ actions_ action_ action_key_ action_name_ action_names_ + local actions_vides_ action_vide_ + local action_quitter_ + local action_par_defaut_ + local choice_ choice_ok_ choice_action_ choice_option_ + local action_varname_ option_varname_ + local sans_options_ message_sans_options_ + + action_varname_="$1"; shift + option_varname_="$1"; shift + if [ -z "$action_varname_" -o -z "$option_varname_" ]; then + OENC="$UTF8" eerror "action_varname et option_varname doivent être spécifiés" + return 1 + fi + + # actions possibles + actions_sep_by_space_="$1"; shift + if [ -z "$actions_sep_by_space_" ]; then + OENC="$UTF8" eerror "actions doit être non vide" + return 1 + fi + + # Construire le tableau des options + options_one_by_line_="$1"; shift + if [ -n "$options_one_by_line_" ]; then + i_=0 + while next_arg options_one_by_line_ option_; do + options_[$i_]="$option_" + i_=$(($i_ + 1)) + done + else + sans_options_=1 + fi + + # autres paramètres + while [ -n "$1" ]; do + ! get_option "$1" "actions_vides action_quitter action_par_defaut votre_choix titre_du_menu message_sans_options" _ && break + shift + done + if [ -n "$sans_options_" -a -z "$actions_vides_" ]; then + OENC="$UTF8" eerror "options doit être non vide" + return 1 + fi + + # Construire le tableau des actions + i_=0 + for action_ in $actions_sep_by_space_; do + action_key_="$(first_char "$action_")" + action_="$(last_chars "$action_")" + if [ "$(first_char "$action_")" == ":" ]; then + action_name_="$(last_chars "$action_")" + action_name_="${action_name_//_/ }" + else + action_name_="$action_key_" + fi + + [ -z "$action_par_defaut_" ] && action_par_defaut_="$action_key_" + [ "$action_key_" == "$action_par_defaut_" ] && action_name_="$action_name_*" + action_names_="${action_names_:+$action_names_/}$action_name_" + + actions_[$i_]="$(echo $action_key_ | awk '{print tolower($0)}')" + i_=$(($i_ + 1)) + done + [ -z "$action_quitter_" ] && action_quitter_="$action_key_" + if [ "$action_par_defaut_" == "$action_quitter_" ]; then + OENC="$UTF8" eerror "action_par_defaut et action_quitter doivent être différents" + return 1 + fi + actions_vides_="$actions_vides_ $action_quitter_" + + # Lecture des choix de l'utilisateur + c_=0 + while true; do + if [ "$c_" == "0" ]; then + # Afficher le menu + [ -n "$titre_du_menu_" ] && tooenc "=== $titre_du_menu_ ===" + if [ -z "$sans_options_" ]; then + i_=1 + for option_ in "${options_[@]}"; do + tooenc "$i_ - $option_" + i_=$(($i_ + 1)) + done + else + if [ -n "$message_sans_options_" ]; then + tooenc "$message_sans_options_" + else + tooenc "Pas d'options disponibles" "$UTF8" + fi + fi + fi + + # Afficher les choix + tooenc_ " +Actions disponibles: " "$UTF8" + tooenc "$action_names_" + if [ -n "$votre_choix_" ]; then + tooenc_ "$votre_choix_" + else + tooenc_ "Entrez le numéro de l'option choisie" "$UTF8" + fi + echo_ ": " + read choice_ + + # vérifier la saisie + choice_option_="$(expr "$choice_" : "\([0-9]*\)")" + if [ -n "$choice_option_" ]; then + # on a donné le numéro de l'option sans l'action. retourner l'action par défaut + if [ -z "$sans_options_" -a "$choice_option_" -gt "0" -a "$choice_option_" -le "${#options_[*]}" ]; then + # numéro d'option correct + choice_option_="${options_[$(($choice_option_ - 1))]}" + choice_action_="$action_par_defaut_" + break + else + OENC="$UTF8" eerror "Numéro d'option incorrect" + fi + + else + choice_action_="$(expr "$choice_" : "\([^ $tab_]\)")" + choice_ok_= + error_displayed_= + for action_ in "${actions_[@]}"; do + if [ "$choice_action_" == "$action_" ]; then + # action correcte + + # est-ce une action vide (une action qui ne requière pas de numéro d'option) + for action_vide_ in $actions_vides_; do + if [ "$choice_action_" == "$action_vide_" ]; then + # retourner comme option tous les arguments de + # l'action vide, en trimant les espaces de début + choice_option_="$(expr "$choice_" : "[^ $tab_][ $tab_]*\(.*\)")" + choice_ok_=1 + break + fi + done + [ -n "$choice_ok_" ] && break + + # lire le numéro d'option associé à l'action + choice_option_="$(expr "$choice_" : "[^ $tab_][ $tab_]*\([0-9]*\)")" + if [ -n "$choice_option_" ] && [ "$choice_option_" -gt "0" -a "$choice_option_" -le "${#options_[*]}" ]; then + # numéro d'option correct + choice_option_="${options_[$(($choice_option_ - 1))]}" + choice_ok_=1 + else + OENC="$UTF8" eerror "Il faut spécifier un numéro d'option correct avec l'action" + error_displayed_=1 + fi + [ -n "$choice_ok_" ] && break + fi + done + [ -n "$choice_ok_" ] && break + [ -z "$error_displayed_" ] && OENC="$UTF8" eerror "Action incorrecte" + fi + + c_=$(($c_ + 1)) + if [ "$c_" == "5" ]; then + echo "" # sauter une ligne toutes les 4 tentatives + c_=0 + fi + done + + set_var "$action_varname_" "$choice_action_" + set_var "$option_varname_" "$choice_option_" +} + +################################################## +# fileopts: opérations sur les fichiers + +function file_get_vars() { + # lire les variables dans un fichier + local OENC="$UTF8" + local done_ prefix_ whole_file_ file_ vars_ var_ script_ + + # traiter les arguments + done_= + while [ -z "$done_" ]; do + case "$1" in + --prefix|-p) + # prefixe pour la variable + prefix_="$2" + shift; shift + ;; + --whole-file|-w) + # lire les variables dans le fichier en entier, et pas seulement + # dans l'en-tête. + whole_file_=1 + shift + ;; + *) + done_=1 + ;; + esac + done + + # obtenir le nom du fichier + file_="$1"; shift + if [ ! -f "$file_" ]; then + # fichier inexistant + ewarn "Fichier inexistant: $file_" + return 1 + fi + + # initialiser les valeurs par défaut + vars_= + while [ -n "$1" ]; do + var_="$1"; shift + vars_="${vars_:+$vars_ }$var_" + set_var "$var_" "$1"; shift + done + + # puis parcourir le fichier pour initialiser la valeur définitive des + # variables + if [ -z "$whole_file_" ]; then + script_="/^ *$/ { exit } +" + fi + + for var_ in $vars_; do + script_="$script_ +"'$0 ~ "^ *#* *" prefix "'"$var_"'=" { + # enlever les caractères inutiles + sub(" *#* *" prefix "'"$var_"'=", "") + # enlever éventuellement les quotes autour de la valeur + if ($0 ~ /^".*" *$/) { + $0 = substr($0, 2) # premier quote + match($0, /" *$/) + $0 = substr($0, 1, RSTART - 1) # dernier quote + } + # remplacer dans les valeurs les occurences de quotes + gsub("\"", "\\\"") + # afficher la ligne à évaluer + print "'"$var_"'=\"" $0 "\"" +} +' + done + + eval "$(uawk -v prefix="$prefix_" "$script_" <"$file_")" +} + +function file_set_vars() { + # écrire les variables dans un fichier. Le fichier *doit exister* + local OENC="$UTF8" + local done_ quote_ comment_ prefix_ whole_file_ file_ var_ script_ + + # traiter les arguments + done_= + while [ -z "$done_" ]; do + case "$1" in + --quote-always|-q) + # toujours mettre les valeurs entre quotes + quote_=1 + shift + ;; + --comment-prefix|-c) + # commentaire à préfixer + comment_="$2" + shift; shift + ;; + --prefix|-p) + # prefixe pour la variable + prefix_="$2" + shift; shift + ;; + --whole-file|-w) + # écrire les variables dans le fichier en entier, et pas seulement + # dans l'en-tête. + whole_file_=1 + shift + ;; + *) + done_=1 + ;; + esac + done + + # obtenir le nom du fichier + file_="$1"; shift + if [ ! -f "$file_" ]; then + # fichier inexistant + ewarn "Fichier inexistant: $file_" + return 1 + fi + + # puis parcourir le fichier pour mettre à jour les valeurs + script_='BEGIN { + # faut-il mettre à jour les variables? + update_vars = 1' + for var_ in "$@"; do + script_="$script_ + ${var_}_done = 0" + done + script_="$script_ +}" + + script_="$script_"' +function quote_value_maybe(value) { + if (quote_always || index(value, " ") != 0) { + return "\"" value "\"" + } else { + return value + } +} + +function write_all_remaining_vars() {' + for var_ in "$@"; do + value="${!var_}" + script_="$script_"' + if (! '"$var_"'_done) { + print comment prefix "'"$var_"'=" quote_value_maybe("'"${value//\"/\\\"}"'") + '"$var_"'_done = 1 + }' + done + script_="$script_ +}" + + if [ -z "$whole_file_" ]; then + script_="$script_"' +/^[ \t]*$/ { + write_all_remaining_vars() + update_vars = 0 +}' + fi + + script_="$script_"' +update_vars && comment == "" && $0 ~ "^ *#* *.*=.*$" { + #comment = gensub("^( *#* *).*=.*$", "\\1", 1) + comment = "" + if (match($0, /^ *#* */) != 0) { + comment = substr($0, RSTART, RLENGTH) + } +}' + + for var_ in "$@"; do + value="${!var_}" + script_="$script_"' +update_vars && ! '"$var_"'_done && $0 ~ "^ *#* *" prefix "'"$var_"'=" { + #$0 = gensub("^( *#* *" prefix "'"$var_"'=).*$", "\\1", 1) + match($0, "^ *#* *" prefix "'"$var_"'=") + $0 = substr($0, RSTART, RLENGTH) + $0 = $0 quote_value_maybe("'"${value//\"/\\\"}"'") + print + '"$var_"'_done = 1 + next +}' + done + + script_="$script_"' +{ + print +} +END { + if (update_vars) { + write_all_remaining_vars() + } +}' + + uawk -v quote_always="$quote_" -v comment="$comment_" -v prefix="$prefix_" "$script_" <"$file_" >"$file_.$$" && + /bin/mv "$file_.$$" "$file_" +} + +function file_get_properties() { + # lire les propriétés d'un fichier de propriété java ou xml + if endswith "$1" .xml; then + file_get_xml_properties "$@" + else + file_get_java_properties "$@" + fi +} + +function file_set_properties() { + # écrire les propriétés d'un fichier de propriété java ou xml + local done_ create_ + while [ -z "$done_" ]; do + case "$1" in + --create|-c) + create_=1 + shift + ;; + *) + done_=1 + ;; + esac + done + if endswith "$1" .xml; then + file_set_xml_properties ${create_:+-c} "$@" + else + file_set_java_properties ${create_:+-c} "$@" + fi +} + + +function file_get_java_properties() { + # lire les propriétés d'un fichier de propriétés java. note: les noms de + # propriété java peuvent contenir le caractère "." mais pas les noms de + # variable bash. La conversion est faite automatiquement. Par exemple:: + # file_get_properties build.properties path.to.package "default value" + # charge la valeur de la propriété dans la variable path_to_package + + # $1=nom du fichier de propriété + # $2..n=propriétés qu'il faut lire et valeurs par défaut de ces propriétés + # $__2*i __=nom de la propriété + # $__2*i+1__=valeur par défaut de la propriété si la valeur n'existe pas + # dans le fichier + local OENC="$UTF8" + local file_="$1"; shift + if [ ! -f "$file_" ]; then + # fichier inexistant + ewarn "Fichier de propriété inexistant: $file_" + return 1 + fi + + local var_ sh_var_ awk_var_ + local script + while [ -n "$1" ]; do + # pour chacune des variables... + var_="$1"; shift + sh_var_="${var_//./_}" # nom de la variable shell traduite ("." --> "_") + sh_var_="${sh_var_//-/_}" # ("-" --> "_") + awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk + + # initialiser la valeur par défaut + set_var "$sh_var_" "$1"; shift + + # et créer le script qui affichera sa valeur + script="$script"' +$0 ~ "^[ \t]*'"$(quote_awk "$awkre_var_")"'=" { + # enlever les caractères de début + sub("^[ \t]*'"$(quote_awk "$awkre_var_")"'=", "") + value = $0 + # éventuellement ajouter les lignes de continuation + while (substr(value, length(value), 1) == "\\") { + getline + sub("^[ \t]*", "") + value = substr(value, 1, length(value) - 1) $0 + } + gsub("\"", "\\\"", value) + print "'"$sh_var_"'=\"" value "\"" +} +' + done + + eval "$(awk "$script" <"$file_")" +} + +function file_set_java_properties() { + # écrire des propriétés dans un fichier de propriétés java. + + # $1=nom du fichier de propriété + # $2..n=propriétés qu'il faut écrire et valeurs de ces propriétés + # $__2*i __=nom de la propriété + # $__2*i+1__=valeur de la propriété + # traiter les arguments + local OENC="$UTF8" + local done_ create_ + while [ -z "$done_" ]; do + case "$1" in + --create|-c) + # créer le fichier s'il n'existe pas + create_=1 + shift + ;; + *) + done_=1 + ;; + esac + done + + local file_="$1"; shift + if [ ! -f "$file_" ]; then + if [ -n "$create_" ]; then + touch "$file_" + else + # fichier inexistant + ewarn "Fichier de propriété inexistant: $file_" + return 1 + fi + fi + + # récupérer les noms des propriétés et leur valeur + local var_ arg_ sh_var_ awkre_var_ value_ + local -a vars_ values_ + value_=vars_ + for arg_ in "$@"; do + array_add "$value_" "$arg_" + if [ "$value_" == "vars_" ]; then + value_=values_ + else + value_=vars_ + fi + done + + # créer le script qui va parcourir le fichier pour mettre à jour les valeurs + script_="BEGIN {" + for var_ in "${vars_[@]}"; do + sh_var_="${var_//./_}" # nom de la variable shell traduite ("." --> "_") + sh_var_="${sh_var_//-/_}" # ("-" --> "_") + script_="$script_ + ${sh_var_}_done = 0" + done + script_="$script_"' +} +function write_all_remaining_vars() {' + local i=0 + while [ $i -lt ${#vars_[*]} ]; do + var_="${vars_[$i]}" + sh_var_="${var_//./_}" # nom de la variable shell traduite ("." --> "_") + sh_var_="${sh_var_//-/_}" # ("-" --> "_") + awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk + value_="${values_[$i]}" + + script_="$script_"' + if (! '"$sh_var_"'_done) { + print "'"$var_"'=" "'"${value_//\"/\\\"}"'" + '"$sh_var_"'_done = 1 + }' + i=$((i + 1)) + done + script_="$script_ +}" + + local i=0 + while [ $i -lt ${#vars_[*]} ]; do + var_="${vars_[$i]}" + sh_var_="${var_//./_}" # nom de la variable shell traduite ("." --> "_") + sh_var_="${sh_var_//-/_}" # ("-" --> "_") + awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk + value_="${values_[$i]}" + script_="$script_"' +! '"$sh_var_"'_done && $0 ~ "^[ \t]*'"$awkre_var_"'=" { + #name = gensub("^([ \t]*'"$awkre_var_"'=).*$", "\\1", 1) + match($0, "^[ \t]*'"$awkre_var_"'=") + name = substr($0, RSTART, RLENGTH) + value = substr($0, length(name) + 1) + + while (substr(value, length(value), 1) == "\\") { + getline + value = value $0 + } + line = name "'"${value_//\"/\\\"}"'" + prefix = "" + max_len = 75 + if (length(line) > max_len) { + do { + print prefix substr(line, 1, max_len) "\\" + line = substr(line, max_len + 1) + prefix = " " + max_len = 71 + } while (length(line) > max_len) + } + print prefix line + '"$sh_var_"'_done = 1 + next +}' + i=$((i + 1)) + done + + script_="$script_ +{ + print +} +END { + write_all_remaining_vars() +}" + + awk "$script_" <"$file_" >"$file_.$$" && + /bin/mv "$file_.$$" "$file_" +} + +function file_get_xml_properties() { + # lire les propriétés d'un fichier de propriétés xml. Limitation: les + # propriétés ne doivent pas être continuées sur plusieurs lignes. Les + # propriétés doivent être écrites sous la forme:: + # propvalue + + # note: les noms de propriété java peuvent contenir le caractère "." mais + # pas les noms de variable bash. La conversion est faite + # automatiquement. Par exemple:: + # file_get_properties build.properties path.to.package "default value" + # charge la valeur de la propriété dans la variable path_to_package + + # $1=nom du fichier de propriété + # $2..n=propriétés qu'il faut lire et valeurs par défaut de ces propriétés + # $__2*i __=nom de la propriété + # $__2*i+1__=valeur par défaut de la propriété si la valeur n'existe pas + # dans le fichier + local OENC="$UTF8" + local file_="$1"; shift + if [ ! -f "$file_" ]; then + # fichier inexistant + ewarn "Fichier de propriété inexistant: $file_" + return 1 + fi + + local var_ sh_var_ awk_var_ + local script + while [ -n "$1" ]; do + # pour chacune des variables... + var_="$1"; shift + sh_var_="${var_//./_}" # nom de la variable shell traduite ("." --> "_") + sh_var_="${sh_var_//-/_}" # ("-" --> "_") + awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk + + # initialiser la valeur par défaut + set_var "$sh_var_" "$1"; shift + + # et créer le script qui affichera sa valeur + script="$script"' +$0 ~ /^[ \t]*<'"$awkre_var_"'>.*<\/'"$awkre_var_"'>/ { + sub(/^[ \t]*<'"$awkre_var_"'>/, "") + sub(/<\/'"$awkre_var_"'>.*$/, "") + gsub("\"", "\\\"", $0) + print "'"$sh_var_"'=\"" $0 "\"" +} +' + done + + eval "$(awk "$script" <"$file_")" +} + +function file_set_xml_properties() { + # écrire des propriétés dans un fichier de propriétés java. + + # $1=nom du fichier de propriété + # $2..n=propriétés qu'il faut écrire et valeurs de ces propriétés + # $__2*i __=nom de la propriété + # $__2*i+1__=valeur de la propriété + # traiter les arguments + local OENC="$UTF8" + local done_ create_ + while [ -z "$done_" ]; do + case "$1" in + --create|-c) + # créer le fichier s'il n'existe pas + create_=1 + shift + ;; + *) + done_=1 + ;; + esac + done + + local file_="$1"; shift + if [ ! -f "$file_" ]; then + if [ -n "$create_" ]; then + touch "$file_" + else + # fichier inexistant + ewarn "Fichier de propriété inexistant: $file_" + return 1 + fi + fi + + # récupérer les noms des propriétés et leur valeur + local var_ arg_ sh_var_ awkre_var_ value_ + local -a vars_ values_ + value_=vars_ + for arg_ in "$@"; do + array_add "$value_" "$arg_" + if [ "$value_" == "vars_" ]; then + value_=values_ + else + value_=vars_ + fi + done + + # créer le script qui va parcourir le fichier pour mettre à jour les valeurs + script_='BEGIN { + rootElement = ""' + for var_ in "${vars_[@]}"; do + sh_var_="${var_//./_}" # nom de la variable shell traduite ("." --> "_") + sh_var_="${sh_var_//-/_}" # ("-" --> "_") + script_="$script_ + ${sh_var_}_done = 0" + done + script_="$script_"' +} +function write_all_remaining_vars() {' + local i=0 + while [ $i -lt ${#vars_[*]} ]; do + var_="${vars_[$i]}" + sh_var_="${var_//./_}" # nom de la variable shell traduite ("." --> "_") + sh_var_="${sh_var_//-/_}" # ("-" --> "_") + awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk + value_="${values_[$i]}" + + script_="$script_"' + if (! '"$sh_var_"'_done) { + print "<'"$var_"'>'"${value_//\"/\\\"}"'" + '"$sh_var_"'_done = 1 + }' + i=$((i + 1)) + done + script_="$script_ +}"' +rootElement == "" { + match($0, /<.*>/) + element = substr($0, RSTART + 1, RLENGTH - 2) + firstChar = substr(element, 1, 1) + if (firstChar != "?" && firstChar != "!") { + rootElement = element + } +}' + + local i=0 + while [ $i -lt ${#vars_[*]} ]; do + var_="${vars_[$i]}" + sh_var_="${var_//./_}" # nom de la variable shell traduite ("." --> "_") + sh_var_="${sh_var_//-/_}" # ("-" --> "_") + awkre_var_="${var_//./\\\\.}" # nom de la variable pour une expression régulière awk + value_="${values_[$i]}" + script_="$script_"' +rootElement != "" && ! '"$sh_var_"'_done && $0 ~ /^[ \t]*<'"$awkre_var_"'>.*<\/'"$awkre_var_"'>/ { + match($0, /^[ \t]*<'"$awkre_var_"'>/) + first = substr($0, RSTART, RLENGTH) + value = substr($0, length(first) + 1) + match(value, /<\/'"$awkre_var_"'>.*$/) + last = substr(value, RSTART, RLENGTH) + value = substr(value, 1, RSTART) + + print first "'"${value_//\"/\\\"}"'" last + '"$sh_var_"'_done = 1 + next +}' + i=$((i + 1)) + done + + script_="$script_"' +rootElement != "" && $0 ~ "" { + rootElement = "" + write_all_remaining_vars() +} +{ + print +} +END { + write_all_remaining_vars() +}' + + awk "$script_" <"$file_" >"$file_.$$" && + /bin/mv "$file_.$$" "$file_" +} + +################################################## +# date: fonction pour afficher la date en français et au format RFC-822 + +function get_date_rfc822() { + if [ -n "$__legacy_date__" ]; then + LC_TIME=C date +"%a, %d %b %Y %H:%M:%S %Z" + else + date -R + fi +} + +function get_date_fr() { + date +"%d/%m/%Y" +} + +function get_time_fr() { + date +"%Hh%M" +} + +function parse_date() { + local value="$1" type="${2:-date}" + local now="$(awk 'BEGIN { print mktime(strftime("%Y %m %d 00 00 00 +0400")) }')" + case "$value" in + +*) + value="$(($now + ${value#+} * 86400))" + ;; + *) + value="$(<<<"$value" awk -F/ '{ + nd = strftime("%d"); nm = strftime("%m"); ny = strftime("%Y") + d = $1 + 0; if (d < 1 || d > 31) d = nd; + if ($2 == "") m = nm; + else { m = $2 + 0; if (m < 1 || m > 12) m = nm; } + if ($3 == "") y = ny; + else { y = $3 + 0; if (y < 100) y = y + 2000; } + print mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d)); + }')" + esac + case "$type" in + d|date) + awk '{ print strftime("%d/%m/%Y", $0 + 0) }' <<<"$value" + ;; + l|ldap) + awk '{ print strftime("%Y%m%d%H%M%S+0400", $0 + 0) }' <<<"$value" + ;; + *) + echo "$value" + ;; + esac +} + +################################################## +# path: fonctions pour gérer les chemins + +function abspath() { + # Retourner un chemin absolu vers $1. Si $2 est non nul et si $1 est un + # chemin relatif, alors $1 est exprimé par rapport à $2, sinon il est + # exprimé par rapport au répertoire courant. + local pwd="$(pwd)" + if [ -n "$2" ]; then + # tout d'abord, calculer le répertoire à partir duquel on exprime les + # chemin relatifs + if [ -d "$2" ]; then + pwd="$(cd "$2"; pwd)" + else + pwd="$(abspath "$2")" + fi + fi + + if [ -e "$1" ]; then + local dn="$(dirname "$1")" bn="$(basename "$1")" + if [ "$bn" == "." ]; then + echo "$(cd "$pwd"; cd "$dn"; pwd)" + elif [ "$bn" == ".." ]; then + echo "$(cd "$pwd"; cd "$dn/.."; pwd)" + elif [ "$dn" == "/" ]; then + echo "/$bn" + else + echo "$(cd "$pwd"; cd "$dn"; pwd)/$bn" + fi + else + if first_char_is "$1" "/"; then + echo "$1" + else + echo "$pwd/$1" + fi + fi +} + +function relpath() { + # Retourner le chemin relatif de $1 par rapport à $2. Si $2 n'est pas + # spécifié, on prend le répertoire courant. Si $1 ou $2 ne sont pas des + # répertoires absolus, il sont transformés en chemins absolus par rapport à + # $3. + # Si $1==$2, retourner une chaine vide + local p="$(abspath "$1" "$3")" cwd="$2" + if [ -z "$cwd" ]; then + cwd="$(pwd)" + else + cwd="$(abspath "$cwd" "$3")" + fi + if [ "$p" == "$cwd" ]; then + echo "" + elif [ "${p#$cwd/}" != "$p" ]; then + echo "${p#$cwd/}" + else + local rp + while [ -n "$cwd" -a "${p#$cwd/}" == "$p" ]; do + rp="${rp:+$rp/}.." + cwd="${cwd%/*}" + done + rp="$rp/${p#$cwd/}" + # ${rp%//} traite le cas $1==/ + echo "${rp%//}" + fi +} + +function deref() { + # Retourner un chemin absolu vers le fichier $1, dans lequel + # toutes les composantes "lien symbolique" ont été supprimées. + local OENC="$UTF8" + + local max_deref=50 + local file="$1" + while [ -L "$file" ]; do + basedir="$(dirname "$file")" + link="$(readlink "$file")" + if first_char_is "$link" "/"; then + # lien absolu + file="$link" + else + # lien relatif + file="$basedir/$link" + fi + + max_deref=$(($max_deref - 1)) + [ $max_deref -eq 0 ] && die "Plus de 50 indirection. Le lien $file est-il récursif?" + done + abspath "$file" +} + +function path_if_test() { + # afficher un chemin si le fichier $2 existe (en utilisant l'opérateur $1) + # dans l'un des chemins absolus $4..n. si $3==relative, afficher le chemin + # relatif, sinon le chemin absolu. note: $3 peut être de la forme + # relative:path, auquel cas le chemin affiché est exprimé relativement à + # path + local op_="$1"; shift + local file_="$1"; shift + local rel_="$1" reldir_=; shift + if beginswith "$rel_" relative; then + reldir_="${rel_#relative}" + if beginswith "$reldir_" :; then + # on a un argument de la forme relative:path + reldir_="${reldir_#:}" + if [ -n "$reldir_" ]; then + reldir_="${reldir_}/" + fi + else + # argument vide ou format non valide + reldir_= + fi + else + rel_= + fi + + while [ -n "$1" ]; do + local basedir_="$1" + if [ $op_ "$basedir_/$file_" ]; then + if [ -n "$rel_" ]; then + echo "$reldir_$file_" + else + echo "$basedir_/$file_" + fi + break + fi + shift + done +} + +function is_archive() { + # tester si l'extension d'un fichier indique que c'est une archive + local name="${1%.zip}" + name="${name%.tgz}" + name="${name%.tar.gz}" + name="${name%.tar}" + name="${name%.tar.bz2}" + name="${name%.jar}" + name="${name%.war}" + name="${name%.ear}" + [ "$name" != "$1" ] +} +function extract_archive() { + # Extraire le contenu de l'archive $1 dans le répertoire ${2:-.} + # Les autres arguments indiquent les fichiers à extraire + local arch="$1" destdir="${2:-.}" + shift; shift + if endswith "$arch" .zip; then + unzip -d "$destdir" "$arch" "$@" || return + elif endswith "$arch" .tgz || endswith "$arch" .tar.gz; then + tar xzf "$arch" -C "$destdir" "$@" || return + elif endswith "$arch" .tbz2 || endswith "$arch" .tar.bz2; then + tar xjf "$arch" -C "$destdir" "$@" || return + elif endswith "$arch" .tar; then + tar xf "$arch" -C "$destdir" "$@" || return + elif endswith "$arch" .jar || endswith "$arch" .war || endswith "$arch" .ear; then + jar xf "$arch" -C "$destdir" "$@" || return + else + return 1 + fi +} + + +################################################## +# array: fonctions pour gérer des tableaux + +# Afficher la commande permettant d'initialiser le tableau $1 avec les valeurs: +# soit du tableau $2, soit de $3..$n si $2=="@" +# S'il n'y a que l'argument $1, alors afficher la commande permettant de +# recréer le tableau $1 +function set_array_cmd() { + [ $# -eq 1 ] && set -- "$1" "$1" + local s_ v_ f_ + s_="$1=("; shift + if [ "$1" == "@" ]; then + shift + else + eval "set -- \"\${$1[@]}\"" + fi + f_=1 + for v_ in "$@"; do + [ -n "$f_" ] && f_= || s_="$s_ " + s_="$s_\"$(quote_arg "$v_")\"" + done + s_="$s_)" + echo "$s_" +} +# Soit $1 un tableau à créer. Si $2=="@", créer le tableau $1 avec les valeurs +# $3..$n. Sinon, créer le tableau $1 avec les valeurs du tableau $2. +# Cette fonction n'existe que comme un pendant de set_var(), mais le véritable +# intérêt est la fonction set_array_cmd(). cf array_copy() pour une version plus +# efficace de la copie de tableaux +function set_array() { + eval "$(set_array_cmd "$@")" +} + +function array_count() { + # retourner le nombre d'éléments du tableau $1 + eval "echo \${#$1[*]}" +} + +function array_isempty() { + # tester si un tableau est vide + test $(array_count "$1") -eq 0 +} + +function array_new() { + # créer un tableau vide dont le nom est $1 + eval "$1=()" +} + +function array_add() { + # ajouter la valeur $2 au tableau dont le nom est $1 + eval "$1=(\"\${$1[@]}\" \"$(quote_arg "$2")\")" +} + +# insérer la valeur $2 au début du tableau dont le nom est $1 +function array_ins() { eval "$1=(\"$(quote_arg "$2")\" \"\${$1[@]}\")"; } + +function array_add_values() { + # Ajouter les valeurs $2 au tableau dont le nom est $1 + # $2 est une liste de valeurs séparées par des espaces ou le caractère ':' + local array_="$1" value_ + if [[ "$2" == *:* ]]; then + local IFS=: + set -- $2 + unset IFS + else + set -- $2 + fi + for value_ in "$@"; do + array_add "$array_" "$value_" + done +} + +function array_del() { + # supprimer les valeurs $2 du tableau dont le nom est $1 + local arg_="$(quote_arg "$2")" value_ + local -a array_ + eval 'for value_ in "${'"$1"'[@]}"; do if [ "$value_" != "'"$arg_"'" ]; then array_add array_ "$value_"; fi; done' + array_copy "$1" array_ +} + +function array_set() { + # ajouter la valeur $2 au tableau dont le nom est $1, si la valeur n'y est + # pas déjà. Retourner vrai si la valeur a été ajoutée + local value_="$(quote_arg "$2")" i_ + eval "for i_ in \"\${$1[@]}\"; do if [ \"\$i_\" == \"$value_\" ]; then return 1; fi; done" + array_add "$1" "$2" + return 0 +} + +function array_contains() { + # tester si le tableau dont le nom est $1 contient la valeur $2 + local value_="$(quote_arg "$2")" i_ + eval "for i_ in \"\${$1[@]}\"; do if [ \"\$i_\" == \"$value_\" ]; then return; fi; done" + return 1 +} + +function array_copy() { + # copier le contenu du tableau $2 dans le tableau $1 + eval "$1=(\"\${$2[@]}\")" +} + +function array_extend() { + # Ajouter le contenu du tableau $2 au tableau $1 + eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")" +} +# dans le tableau $1, remplacer toutes les occurences de $2 par $3..* +function array_replace() { + local srcname_="$1"; shift + local from_="$1"; shift + local -a dest_ + local src_ v_ + src_="$srcname_[@]" + for value_ in "${!src_}"; do + if [ "$value_" == "$from_" ]; then + dest_=("${dest_[@]}" "$@") + else + dest_=("${dest_[@]}" "$value_") + fi + done + array_copy "$srcname_" dest_ +} +# Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les +# arguments '$v $3..$n' +function array_each() { + local an_="$1"; shift + local f_="$1"; shift + local a_="$an_[@]" v + for v_ in "${!a_}"; do + "$f_" "$v_" "$@" + done +} +# retourner la première valeur du tableau $1 +function first_value() { eval "echo \"\${$1[@]:0:1}\""; } +# retourner la dernière valeur du tableau $1 +function last_value() { eval "echo \"\${$1[@]:\$((-1)):1}\""; } +# copier le contenu du tableau $2 dans le tableau $1 +function array_copy() { eval "$1=(\"\${$2[@]}\")"; } +# copier tous les valeurs du tableau $2(=$1) dans le tableau $1, excepté la dernière +function array_copy_firsts() { eval "$1=(\"\${${2:-$1}[@]:0:\$((\${#${2:-$1}[@]}-1))}\")"; } +function array_del_last() { array_copy_firsts "$1"; } +# copier tous les valeurs du tableau $2(=$1) dans le tableau $1, excepté la première +function array_copy_lasts() { eval "$1=(\"\${${2:-$1}[@]:1}\")"; } +function array_del_first() { array_copy_lasts "$1"; } +# ajouter le contenu du tableau $2 au tableau $1 +function array_extend() { eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")"; } +# ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la dernière +function array_extend_firsts() { eval "$1=(\"\${$1[@]}\" \"\${$2[@]:0:\$((\${#$2[@]}-1))}\")"; } +# ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la première +function array_extend_lasts() { eval "$1=(\"\${$1[@]}\" \"\${$2[@]:1}\")"; } +# Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les +# arguments '$v $3..$n', et remplacer la valeur par le résultat de la fonction +function array_map() { + local an_="$1"; shift + local f_="$1"; shift + local a_="$an_[@]" v + local -a vs_ + for v_ in "${!a_}"; do + vs_=("${vs_[@]}" "$("$f_" "$v_" "$@")") + done + array_copy "$an_" vs_ +} + +function array_from_args() { + # créer le tableau dont le nom est $1 avec les arguments à partir de $2 + local array_="$1"; shift + eval "$array_"'=("$@")' +} + +function array_from_file() { + # créer le tableau dont le nom est $1 avec les lignes du fichier $2 + # si $3=all, ne pas supprimer les lignes vide ou de commentaire + eval "$(<"$2" uawk -v name="$1" -v all="$3" ' +BEGIN { + print name "=(" +} +all != "all" && $0 ~ /^$/ { next } +all != "all" && $0 ~ /^#/ { next } +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +} +END { + print ")" +}')" #" +} + +function array_from_lines() { + # créer le tableau $1 avec chaque ligne de $2. Les lignes vides sont + # ignorés. + eval "$(<<<"$2" uawk -v name="$1" ' +BEGIN { + print name "=(" +} +/^$/ { next } +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +} +END { + print ")" +}')" #" +} + +function array_split() { + # créer le tableau $1 avec chaque élément de $2 (un ensemble d'éléments + # séparés par $3, qui vaut ':' par défaut). Les éléments vides sont + # ignorés. par exemple "a::b" est équivalent à "a:b" + eval "$(<<<"$2" uawk -v RS="" -v ORS="" '{ gsub("\r*\n$", ""); print }' | +uawk -v name="$1" -v RS="${3:-:}" ' +BEGIN { + print name "=(" +} +/^$/ { next } +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +} +END { + print ")" +}')" #" +} + +function array_from_path() { + array_split "$1" "$2" ":" +} + +function array_join() { + # afficher le contenu du tableau dont le nom est $1 sous forme d'une liste + # de chemins séparés par $2 + # Si $1=="@", alors les éléments du tableaux sont les arguments de la + # fonction à partir de $3 + # Si $1!="@" et que le tableau est vide, afficher $3 + # Si $1!="@", $4 et $5 sont des préfixes et suffixes à rajouter à chaque élément + local array_ line_ joined_ sep_="${2:-,}" pfix_ sfix_ + if [ "$1" == "@" ]; then + array_="\$@" + shift; shift + else + array_="\${$1[@]}" + pfix_="$4" + sfix_="$5" + fi + eval 'for line_ in "'"$array_"'"; do joined_="${joined_:+$joined_'"$sep_"'}$pfix_$line_$sfix_"; done' + if [ -n "$joined_" ]; then + echo "$joined_" + elif [ "$array_" != "\$@" -a -n "$3" ]; then + echo "$3" + fi +} + +function array_to_lines() { + # afficher le tableau dont le nom est $1 sous forme de lignes + array_join "$1" " +" "$2" "$3" "$4" +} + +function array_to_path() { + # afficher le tableau dont le nom est $1 sous forme d'une liste de chemins + # séparés par ':') + array_join "$1" ":" "$2" "$3" "$4" +} + +function array_enum_start() { + # Commencer l'énumération du tableau $1 + [ -z "$1" ] && return 1 + set_var_literal "${1}_enum_index" 0 +} + +function array_enum_next() { + # Soit la variable array dont le nom est donné dans $1, et la variable next + # dont le nom est donné dans $2. Mettre à jour next avec l'élément + # $1_enum_index du tableau array, supprimer cet élément, et incrémenter + # $1_enum_index. Retourner faux s'il n'y a plus d'éléments dans le tableau + + local arrayname_="$1" varname_="$2" + [ -z "$arrayname_" -o -z "$varname_" ] && return 1 + + eval "local count_=\${#$arrayname_[*]}" + [ $count_ -eq 0 ] && return 1 + + # calculer l'index de départ + local index_="${arrayname_}_enum_index" + [ -z "${!index_}" ] && set_var_literal "$index_" 0 + # lire la valeur + local item_="$arrayname_[${!index_}]" + set_var "$varname_" "${!item_}" + # la supprimer du tableau + unset "$item_" + # incrémenter le compteur + set_var_literal "$index_" $((${!index_} + 1)) +} + +function __array_ls() { + # Lister les fichiers avec `list_$1 $3 $4...`, et les mettre dans le tableau $2 + # Le tableau contient les chemins complets, par seulement les noms comme avec list_$1 + local list_="list_${1:-all}"; shift + local arrayname_="$1"; shift + local basedir_="${1:-.}"; shift + local -a files_ + array_from_lines files_ "$("$list_" "$basedir_" "$@")" + local file_ + array_new "$arrayname_" + for file_ in "${files_[@]}"; do + array_add "$arrayname_" "$basedir_/$file_" + done +} + +function array_lsall() { + # Lister les fichiers avec `list_all $2 $3...`, et les mettre dans le tableau $1 + # Le tableau contient les chemins complets, par seulement les noms comme avec list_all + __array_ls all "$@" +} + +function array_lsdirs() { + # Lister les fichiers avec `list_dirs $2 $3...`, et les mettre dans le tableau $1 + # Le tableau contient les chemins complets, par seulement les noms comme avec list_dirs + __array_ls dirs "$@" +} + +function array_lsfiles() { + # Lister les fichiers avec `list_files $2 $3...`, et les mettre dans le tableau $1 + # Le tableau contient les chemins complets, par seulement les noms comme avec list_files + __array_ls files "$@" +} + +################################################## +# Accès à une resource web + +function _dumpurl_method_available() { + # $1=dumpurl_method + local m="$1" + if [ "$m" == "curl" ]; then + progexists curl || [ -n "$__curl_EXISTS__" ] + elif [ "$m" == "wget" ]; then + if progexists wget || [ -n "$__wget_EXISTS__" ]; then + # vérifier la version ce doit être >= 1.9 + wget --version | grep "^GNU Wget" | awk '{ + v = $0 + major = 0 + minor = 0 + if (match(v, /[0-9][0-9]*/) != 0) { + major = substr(v, RSTART, RLENGTH) + 0 + v = substr(v, RSTART + RLENGTH + 1) + } + if (match(v, /[0-9][0-9]*/) != 0) { + minor = substr(v, RSTART, RLENGTH) + 0 + v = substr(v, RSTART + RLENGTH + 1) + } + + if (major >= 1 && minor >= 9) { + exit 0 + } else { + exit 1 + } +}' + fi + else + return 1 + fi +} + +function dumpurl_method() { + if [ -z "$DUMPURL_METHOD" ]; then + local DUMPURL_METHOD + if _dumpurl_method_available curl; then + DUMPURL_METHOD=curl + elif _dumpurl_method_available wget; then + DUMPURL_METHOD=wget + ## XXX telnet non supporté pour le moment + #elif progexists telnet; then + # DUMPURL_METHOD=telnet + fi + fi + echo "$DUMPURL_METHOD" +} + +function dumpurl_available() { + # retourner vrai si dumpurl est disponible (si wget ou curl sont trouvés) + _dumpurl_method_available "$(dumpurl_method)" +} + +function dumpurl() { + # afficher le résultat du téléchargement de l'url $1, ou une chaine vide si + # une erreur s'est produite. + # l'option -m choisit la méthode: GET ou POST (suivi de la chaine à envoyer) + # l'option -H permet d'ajouter des en-têtes + + local done= http_method=GET postdata headers header url no_proxy + local -a headers + while [ -n "$1" ]; do + case "$1" in + --) + shift + done=1 + ;; + + -m) + shift + http_method="$1" + if [ "$http_method" == "POST" ]; then + postdata="$2" + shift + fi + ;; + + -H) + shift + array_add headers "$1: $2" + shift + ;; + + -X) + no_proxy=1 + ;; + + -*) + #ewarn "option non reconnue: $1" + ;; + + *) + done=1 + ;; + esac + [ -n "$done" ] && break + shift + done + url="$1" + + dumpurl_available || return 1 + local dumpurl_method="$(dumpurl_method)" + + if [ "$dumpurl_method" == "curl" ]; then + curl="curl${no_proxy:+ -x ''}" + for header in "${headers[@]}"; do + curl="$curl --header \"$(quote_arg "$header")\"" + done + if [ "$http_method" == "GET" ]; then + : + elif [ "$http_method" == "POST" ]; then + curl="$curl --data-binary \"$(quote_arg "$postdata")\"" + else + # méthode inconnue + return 1 + fi + curl="$curl -f -s \"$(quote_arg "$url")\"" + + eval "$curl" + + elif [ "$dumpurl_method" == "wget" ]; then + wget="wget${no_proxy:+ --no-proxy}" + for header in "${headers[@]}"; do + wget="$wget --header=\"$(quote_arg "$header")\"" + done + if [ "$http_method" == "GET" ]; then + : + elif [ "$http_method" == "POST" ]; then + wget="$wget --post-data=\"$(quote_arg "$postdata")\"" + else + # méthode inconnue + return 1 + fi + wget="$wget -q -O - \"$(quote_arg "$url")\"" + + eval "$wget" + + # XXX pas encore implémenté + #elif [ "$dumpurl_method" == "telnet" ]; then + # return 1 + + else + return 1 + fi +} diff --git a/legacy/sysinc/java b/legacy/sysinc/java new file mode 100644 index 0000000..7d6a651 --- /dev/null +++ b/legacy/sysinc/java @@ -0,0 +1,278 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require sysinc/base +##@require sysinc/functions + +################################################################################ +# versions installées sur le système +JAVA_VMS14=(); JAVA_HOMES14=() +JAVA_VMS5=(); JAVA_HOMES5=() +JAVA_VMS6=(); JAVA_HOMES6=() +JAVA_VMS7=(); JAVA_HOMES7=() +# versions locales 32 bits +JAVA32_VMS14=(); JAVA32_HOMES14=() +JAVA32_VMS5=(); JAVA32_HOMES5=() +JAVA32_VMS6=(); JAVA32_HOMES6=() +JAVA32_VMS7=(); JAVA32_HOMES7=() +# versions locales 64 bits +JAVA64_VMS5=(); JAVA64_HOMES5=() +JAVA64_VMS6=(); JAVA64_HOMES6=() +JAVA64_VMS7=(); JAVA64_HOMES7=() +# version sélectionnée +SELECTED_JAVA_VM=; SELECTED_JAVA_HOME= +SELECTED_JAVA=; SELECTED_JAVAC= + +function __add() { + # Ajouter à la liste JAVA$3_$1 la valeur $2 + array_add "JAVA${3}_${1}" "$2" +} + +function __compute_java_vms() { + # calculer la liste de VMs disponibles et initialiser les tableaux + # JAVA{,32,64}_{VMS,HOMES}{14,5,6,7} + [ -z "$__COMPUTED_JAVA_VMS" ] || return + + local vms vm v + if [ "$SYSTEM_NAME" == "Linux" ]; then + if [ "$LINUX_FLAVOUR" == "gentoo" ]; then + # sur gentoo + array_from_lines vms "$(list_dirs /usr/lib/jvm)" + for vm in "${vms[@]}"; do + v="${vm#sun-jdk-}" + if [ "$v" == 1.4 ]; then + __add VMS14 "$vm" + __add HOMES14 "/usr/lib/jvm/$vm" + elif [ "$v" == 1.5 ]; then + __add VMS5 "$vm" + __add HOMES5 "/usr/lib/jvm/$vm" + elif [ "$v" == 1.6 ]; then + __add VMS6 "$vm" + __add HOMES6 "/usr/lib/jvm/$vm" + elif [ "$v" == 1.7 ]; then + __add VMS7 "$vm" + __add HOMES7 "/usr/lib/jvm/$vm" + fi + done + elif [ "$LINUX_FLAVOUR" == "debian" ]; then + # sur debian + array_from_lines vms "$(list_dirs /usr/lib/jvm)" + for vm in "${vms[@]}"; do + v="${vm%-sun}" + v="${v#java-}" + v="${v#j2sdk}" + if [ "$v" == 1.4 ]; then + __add VMS14 "$vm" + __add HOMES14 "/usr/lib/jvm/$vm" + elif [ "$v" == 1.5 -o "$v" == 1.5.0 ]; then + __add VMS5 "$vm" + __add HOMES5 "/usr/lib/jvm/$vm" + elif [ "$v" == 6 -o "$v" == 1.6 ]; then + __add VMS6 "$vm" + __add HOMES6 "/usr/lib/jvm/$vm" + elif [ "$v" == 7 -o "$v" == 1.7 ]; then + __add VMS7 "$vm" + __add HOMES7 "/usr/lib/jvm/$vm" + fi + done + if [ -d /usr/lib/j2sdk1.4-sun ]; then + __add VMS14 j2sdk1.4-sun + __add HOMES14 /usr/lib/j2sdk1.4-sun + fi + fi + array_from_lines vms "$(list_dirs "$HOME/opt/jvm32")" + for vm in "${vms[@]}"; do + v="${vm#sun-jdk-}" + if [ "$v" == 1.4 ]; then + __add VMS14 "$vm" 32 + __add HOMES14 "$HOME/opt/jvm32/$vm" 32 + elif [ "$v" == 1.5 ]; then + __add VMS5 "$vm" 32 + __add HOMES5 "$HOME/opt/jvm32/$vm" 32 + elif [ "$v" == 1.6 ]; then + __add VMS6 "$vm" 32 + __add HOMES6 "$HOME/opt/jvm32/$vm" 32 + elif [ "$v" == 1.7 ]; then + __add VMS7 "$vm" 32 + __add HOMES7 "$HOME/opt/jvm32/$vm" 32 + fi + done + array_from_lines vms "$(list_dirs "$HOME/opt/jvm64")" + for vm in "${vms[@]}"; do + v="${vm#sun-jdk-}" + if [ "$v" == 1.5 ]; then + __add VMS5 "$vm" 64 + __add HOMES5 "$HOME/opt/jvm64/$vm" 64 + elif [ "$v" == 1.6 ]; then + __add VMS6 "$vm" 64 + __add HOMES6 "$HOME/opt/jvm64/$vm" 64 + elif [ "$v" == 1.7 ]; then + __add VMS7 "$vm" 64 + __add HOMES7 "$HOME/opt/jvm64/$vm" 64 + fi + done + elif [ "$SYSTEM_NAME" == "Darwin" ]; then + if [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/1.4" ]; then + __add VMS14 "1.4" + __add HOMES14 "/System/Library/Frameworks/JavaVM.framework/Versions/1.4/Home" + fi + if [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/1.5" ]; then + __add VMS5 "1.5" + __add HOMES5 "/System/Library/Frameworks/JavaVM.framework/Versions/1.5/Home" + fi + if [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/1.6" ]; then + __add VMS6 "1.6" + __add HOMES6 "/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home" + fi + if [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/1.7" ]; then + __add VMS7 "1.7" + __add HOMES7 "/System/Library/Frameworks/JavaVM.framework/Versions/1.7/Home" + fi + fi + __COMPUTED_JAVA_VMS=1 +} + +function __select_vms() { + if [ -z "$2" ]; then + vms="JAVA_VMS$1[@]"; vms=("${!vms}") + if [ -n "${vms[*]}" ]; then + homes="JAVA_HOMES$1[@]"; homes=("${!homes}") + return 0 + else + return 1 + fi + else + vms="JAVA${2}_VMS$1[@]"; vms=("${!vms}") + homes="JAVA${2}_HOMES$1[@]"; homes=("${!homes}") + custom=1 + [ -n "${vms[*]}" ] && return 0 || return 1 + fi +} + +function __select_java() { + # Sélectionner la version de java $1 (qui peut être 14, 5, 6 ou 7) + # Si $2 est défini, il peut s'agit de 32 ou 64 selon que l'on requière la + # version 32bits ou 64 bits. Sinon, la version sélectionnée peut être 32bits + # ou 64bits selon ce qui est disponible. + __compute_java_vms + + local vms homes custom + if [ -z "$2" ]; then + __select_vms "$1" || + __select_vms "$1" "$SYSTEM_BITS" || + __select_vms "$1" 32 || + __select_vms "$1" 64 + else + __select_vms "$1" "$2" + fi + if [ -n "${vms[*]}" ]; then + SELECTED_JAVA_VM="${vms[0]}" + SELECTED_JAVA_HOME="${homes[0]}" + SELECTED_JAVA="$SELECTED_JAVA_HOME/bin/java" + SELECTED_JAVAC="$SELECTED_JAVA_HOME/bin/javac" + if [ -z "$custom" -a "$LINUX_FLAVOUR" == "gentoo" ]; then + export GENTOO_VM="$SELECTED_JAVA_VM" + export JAVA=java + export JAVAC=javac + return 0 + else + [ "$LINUX_FLAVOUR" == "gentoo" ] && unset GENTOO_VM + export JAVA_HOME="$SELECTED_JAVA_HOME" + export JAVA="$SELECTED_JAVA" + export JAVAC="$SELECTED_JAVAC" + export PATH="$JAVA_HOME/bin:$PATH" + return 0 + fi + fi + return 1 +} + +function __select_default_java() { + # Sélectionner la version de java par défaut. Si JAVA_HOME est défini, + # prendre cette valeur. Sinon, essayer dans l'ordre 5, 6, 7, puis 14 + if [ -n "$JAVA_HOME" ]; then + export JAVA="$JAVA_HOME/bin/java" + export JAVAC="$JAVA_HOME/bin/java" + return 0 + fi + local vm + for vm in 5 6 7 14; do + __select_java "$vm" && return 0 + done + return 1 +} + +function select_java() { + # sélectionner la version *minimum* de java correspondant à $1 + # $1== 1.4|1.4+|1.5|1.5+|1.6|1.6+|1.7|1.7+ + # Si $2 est défini, il peut s'agit de 32 ou 64 selon que l'on requière la + # version 32bits ou 64 bits + local v vms homes + + case "$1" in + 1.4|1.4+) + for v in 14 5 6 7; do + __select_java "$v" "$2" && return 0 + done + ;; + 1.5|1.5+) + for v in 5 6 7; do + __select_java "$v" "$2" && return 0 + done + ;; + 1.6|1.6+) + for v in 6 7; do + __select_java "$v" "$2" && return 0 + done + ;; + 1.7|1.7+) + for v in 7; do + __select_java "$v" "$2" && return 0 + done + ;; + esac + return 1 +} + +function select_java_exact() { + # sélectionner la version *exacte* de javac correspondant à $1 + # $1== 1.4|1.5|1.6|1.7 pour une correspondance exacte + # $1== 1.4+|1.5+|1.6+|1.7+ pour une version minimum + # Si $2 est défini, il peut s'agit de 32 ou 64 selon que l'on requière la + # version 32bits ou 64 bits + local v vms homes + + case "$1" in + 1.4) + __select_java 14 && return 0 + ;; + 1.4+) + for v in 14 5 6 7; do + __select_java "$v" "$2" && return 0 + done + ;; + 1.5) + __select_java 5 "$2" && return 0 + ;; + 1.5+) + for v in 5 6 7; do + __select_java "$v" "$2" && return 0 + done + ;; + 1.6) + __select_java 6 "$2" && return 0 + ;; + 1.6+) + for v in 6 7; do + __select_java "$v" "$2" && return 0 + done + ;; + 1.7) + __select_java 7 "$2" && return 0 + ;; + 1.7+) + for v in 7; do + __select_java "$v" "$2" && return 0 + done + ;; + esac + return 1 +} diff --git a/legacy/sysinc/private/init b/legacy/sysinc/private/init new file mode 100644 index 0000000..a416843 --- /dev/null +++ b/legacy/sysinc/private/init @@ -0,0 +1,65 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# requiere l'utilisation de bash pour lancer ce script +use_bash=1 +if [ -n "$BASH" ]; then + if [ `basename "$BASH"` != sh ]; then + use_bash= + fi +fi +if [ -n "$use_bash" ]; then + default_bash="`which bash 2>/dev/null`" + for bash in "$default_bash" /bin/bash /usr/bin/bash /usr/local/bin/bash; do + if [ -x "$bash" ]; then + exec "$bash" "$0" "$@" + fi + done + echo "error: Ce script necessite bash" + exit 1 +fi +unset use_bash + +# Mettre en place l'environnement pour l'utilisation des fonctions de +# utools. + +# répertoire de base des outils utools. +UTOOLS_BASEDIR="$(cd "$(dirname "$0")"; pwd)" + +# lance-t-on ce script en mode développement? +testing="@@dest@@" +if [ "$testing" == "@@"dest"@@" ]; then + export PYTHONPATH="$UTOOLS_BASEDIR/pyutools:$UTOOLS_BASEDIR/pyutools_old:${PYTHONPATH:+:$PYTHONPATH}" + export UPDATEINCPATH="$UTOOLS_BASEDIR${UPDATEINCPATH:+:$UPDATEINCPATH}" +fi + +# fonction die(). peut être remplacée par la version de sysinc/base si ce +# fichier est inclus. +function die() { test -n "$*" && echo "error: $@"; exit 1; } + +# définir la fonction source_functions() pour charger les fonctions de utools à +# partir du bon répertoire +UTOOLS_FUNCTIONS="base system_caps functions" +function utools_source_functions() { + # sourcer un fichier de utools. le mot spécial ALL en première position + # signifie tous les fichiers de $UTOOLS_FUNCTIONS, pris dans sysinc/ + if [ "$1" = "ALL" ]; then + local file + for file in $UTOOLS_FUNCTIONS; do + source "$UTOOLS_BASEDIR/sysinc/$file" || die + done + shift + fi + local script="$(cd "$(dirname "$0")"; pwd)/$(basename "$0")" + while [ -n "$1" ]; do + if [ -f "$UTOOLS_BASEDIR/lib/${1}_functions.sh" ]; then + source "$UTOOLS_BASEDIR/lib/${1}_functions.sh" || die + else + if [ "$script" == "$UTOOLS_BASEDIR/$1" ]; then + die "Inclusion recursive de $1" + else + source "$UTOOLS_BASEDIR/$1" || die + fi + fi + shift + done +} diff --git a/legacy/sysinc/scripts b/legacy/sysinc/scripts new file mode 100644 index 0000000..ad4542e --- /dev/null +++ b/legacy/sysinc/scripts @@ -0,0 +1,157 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function __lf_get_age() { + y=$(date "+%Y") + dy=$(date "+%j"); while [ ${dy#0} != $dy ]; do dy=${dy#0}; done + h=$(date "+%H"); while [ ${h#0} != $h ]; do h=${h#0}; done + echo $((($y * 365 + $dy) * 24 + $h)) +} + +function lockfile_set() { + # USAGE + # lockfile_set [-h max_hours] /path/to/lockfile + # OPTIONS + # lockfile + # fichier qui doit contenir le verrou + # -h max_hours + # Nombre d'heures (par défaut 4) au bout duquel on retourne 2 + # Sinon, on retourne 1. + # Retourne 0 si le verrou a été placé correctement. Le fichier sera supprimé + # automatiquement en fin de script. + # Retourne 1 si le verrou n'a pas pu être placé + # Retourne 2 si le verrou n'a pas pu être placé au bout de max_hours heures + local OENC="$UTF8" + local end_of_options lockfile max_hours=4 + while [ -n "$1" ]; do + case "$1" in + -h) + shift + max_hours="$1" + ;; + --) + shift + end_of_options=1 + ;; + + -*) + if ! set_verbosity "$1" -v; then + ewarn "option non reconnue: $1" + fi + ;; + + *) + end_of_options=1 + ;; + esac + [ -n "$end_of_options" ] && break + shift + done + + lockfile="$1" + [ -n "$lockfile" ] || return 1 + + local now="$(__lf_get_age)" + if [ -f "$lockfile" ]; then + local prev=$(<"$lockfile") diff="$(($now - $prev))" + [ "$diff" -gt "$max_hours" ] && return 2 || return 1 + fi + touch "$lockfile" || return 1 + autoclean "$lockfile" + echo "$now" >"$lockfile" +} + +function pidfile_set() { + # USAGE + # pidfile_set [-p pid] /path/to/pidfile + # OPTIONS + # pidfile + # fichier qui doit contenir le pid du script + # -p pid + # spécifier le pid. par défaut, utiliser $$ + # -r + # si pidfile existe mais que le processus ne tourne plus, faire + # comme si le fichier n'existe pas. + # Retourner 0 si le pid a été correctement écrit dans le fichier. Ce fichier + # sera supprimmé automatiquement en fin de script + # Retourner 1 si le fichier existe déjà et que le processus est en train de + # tourner. + # Retourner 2 si le fichier existe déjà mais que le processus ne tourne + # plus. + # Retourner 10 si autre erreur grave s'est produite (par exemple, s'il manque + # le chemin vers pidfile, ou si le fichier n'est pas accessible en + # écriture.) + local OENC="$UTF8" + local end_of_options pidfile pid=$$ replace= + while [ -n "$1" ]; do + case "$1" in + -p) + shift + pid="$1" + ;; + -r) + replace=1 + ;; + --) + shift + end_of_options=1 + ;; + + -*) + if ! set_verbosity "$1" -v; then + ewarn "option non reconnue: $1" + fi + ;; + + *) + end_of_options=1 + ;; + esac + [ -n "$end_of_options" ] && break + shift + done + + pidfile="$1" + [ -n "$pidfile" ] || return 10 + + if [ -f "$pidfile" ]; then + local curpid="$(<"$pidfile")" + if is_running "$curpid"; then + return 1 + elif [ -n "$replace" ]; then + /bin/rm -f "$pidfile" || return 10 + else + return 2 + fi + fi + + echo_ "$pid" >"$pidfile" || return 10 + autoclean "$pidfile" + return 0 +} + +function read_mainClasses() { + # USAGE + # read_mainClasses scriptName /path/to/mainClasses + # Lire le fichier mainClasses et initialiser les variables correspondant à + # scriptName. + # Retourner 1 si une erreur quelconque se produit: l'entrée correspondant à + # scriptName n'a pas été trouvée, il manque des arguments, ou le fichier + # mainClasses n'existe pas. + # Les lignes dans le fichier mainClasses doivent être de la forme + # scriptName:mainClass[;var0=value0;...] + local OENC="$UTF8" + local scriptname_="$1" mainClasses_="$2" + [ -n "$scriptname_" -a -n "$mainClasses_" ] || return 1 + + local line_="$(grep "^$scriptname_:" "$mainClasses_")" || return 1 + [ -n "$line_" ] || return 1 + + #scriptname="${line_%%:*}" + line_="${line_#*:}" + mainClass="${line_%%;*}" + line_="${line_#*;}" + if [ "$line_" != "$mainClass" ]; then + # Evaluer les variables + eval "$line_" + fi +} diff --git a/legacy/sysinc/system_caps b/legacy/sysinc/system_caps new file mode 100644 index 0000000..1e2a4e4 --- /dev/null +++ b/legacy/sysinc/system_caps @@ -0,0 +1,439 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require base + +# system_caps: Déterminer les capacités du syteme sur lequel nous tournons. Ces +# scripts sont prévus pour adapter un système non Linux pour qu'il fonctionne a +# peu près comme Linux. Les fonctions de base qui doivent être adaptées le sont +# en fonction du système sur lequel on tourne. Il suffit d'inclure ce fichier +# pour bénéficier des nouvelles fonctionnalités + +# note: sous Linux, ces scripts sont essentiellement un no-op + +__SYSTEM_CAPS_COMPUTED__= +if [ -z "$__MAKE_SYSTEM_CAPS__" -a -z "$__FORCE_COMPUTE_SYSTEM_CAPS__" -a -f "@@dest@@/legacy/system_caps" ]; then + source "@@dest@@/legacy/system_caps" +fi + +unset __FORCE_COMPUTE_SYSTEM_CAPS__ +if [ -z "$__SYSTEM_CAPS_COMPUTED__" ]; then + if [ -n "$__MAKE_SYSTEM_CAPS__" ]; then + >"$__MAKE_SYSTEM_CAPS__" + fi + + function __define_and_eval__() { + if [ -n "$__MAKE_SYSTEM_CAPS__" ]; then + echo "$1" >>"$__MAKE_SYSTEM_CAPS__" + fi + eval "$1" + } + + function __verify_system__() { + # Vérifier que le système est supporté + local OENC="$UTF8" + local system + for system in Linux SunOS Darwin Cygwin Mingw unsupported; do + [ "$system" = "$SYSTEM_NAME" ] && break + [ "$system" = "unsupported" ] && die "$SYSTEM_NAME: Système non supporté" + done + } + __verify_system__; unset -f __verify_system__ + + function __get_which_caps__() { + [ "$SYSTEM_NAME" = "Linux" ] && return + + # Obtenir les capacités de which: certaines implémentations affichent "no x + # in ..." sur stdout au lieu de l'afficher sur stderr + local OENC="$UTF8" + local tmpout="$TMPDIR/which_test.$$.out" + local tmperr="$TMPDIR/which_test.$$.err" + which __une_appli_qui_existe_pas__ >"$tmpout" 2>"$tmperr" + if [ -s "$tmpout" ]; then + __legacy_which__=1 # legacy + elif [ -s "$tmperr" ]; then + : # cas normal + else + __legacy_which__=2 # MacOSX 10.5 + fi + /bin/rm -f "$tmpout" + /bin/rm -f "$tmperr" + + if [ "$__legacy_which__" == 1 ]; then + local grep="${grep:-`which grep 2>&1`}" + if [ -x "$grep" ]; then + __define_and_eval__ 'function progexists() { test -n "$1" -a -z "$(which "$1" 2>&1 | '"$grep"' "^no $1")"; }' + else + die "grep est requis pour l'implémentation de progexists() sur ce système" + fi + elif [ "$__legacy_which__" == 2 ]; then + __define_and_eval__ 'function progexists() { which "$1" >&/dev/null; }' + fi + } + __get_which_caps__; unset -f __get_which_caps__ + + function __set_prog_func__() { + # Tester la présence d'un programme sur le système en cours, et si + # le programme doit être accedé avec un chemin absolu, créer une + # fonction du nom de ce programme qui pourra être utilisée pour + # appeler ce programme. + + # $1 == nom de la fonction a créer. Il s'agit géneralement du nom du + # programme, mais ce n'est pas une obligation. + + # $2 == nom à afficher si le programme n'est pas trouvé. Le message + # affiché sera de la forme "Ce script requière $2....". Si cette + # valeur est non vide, le programme *doit exister* sinon le script + # se termine. Si la valeur est "-", alors il s'agit de $1. + + # $3..$n == chemins dans lesquels il faut chercher le programme. Si + # la valeur est "-", on teste si le programme est disponible dans le + # path. + local OENC="$UTF8" + local prog="$1"; shift + local req="$1"; shift + + if [ -z "$prog" ]; then + die "USAGE: __set_prog_func__ prog_func req? path [paths...]" + fi + + if [ -n "${!prog}" ]; then + # Si une variable de ce nom est deja definie, on assume qu'il + # s'agit du chemin vers le programme + __define_and_eval__ 'function '"$prog"'() { '"${!prog}"' "$@"; }' + return 0 + fi + + local found= + while [ -n "$1" ]; do + local path="$1"; shift + + if [ "$path" = "--legacy" ]; then + # Declarer que les programmes qui suivent sont "legacy" + __define_and_eval__ 'export __legacy_'"$prog"'__=1' + continue + fi + + if [ "${path#/}" = "$path" ] && progexists "$path"; then + # Si on donne un nom de programme, et que ce programme se + # trouve dans le path, l'adopter avec son chemin absolu + if [ "$path" != "$prog" ]; then + path="$(type -p "$path")" + __define_and_eval__ 'function '"$prog"'() { "'"$path"'" "$@"; }' + fi + found=1 + break + fi + + if [ "$path" = "-" ] && progexists "$prog"; then + # pas la peine de creer une fonction, la commande existe + # deja avec ce nom-la dans le path + found=1 + break + fi + + if [ -d "$path" ]; then path="$path/$prog"; fi + if [ -d "$path" ]; then continue; fi + + if [ -x "$path" ]; then + __define_and_eval__ 'function '"$prog"'() { "'"$path"'" "$@"; }' + found=1 + break + fi + done + + if [ -z "$found" -a -n "$req" ]; then + if [ "$req" = "-" ]; then req="$prog"; fi + die "Ce script requière $req +Si ce programme est installé, faites + export $prog=/path/to/$prog +et relancez ce script" + fi + + test -n "$found" + } + + function __get_tools_path_and_caps__() { + [ "$SYSTEM_NAME" = "Linux" ] && return + + # Obtenir l'emplacement d'outils standards, tels que: + # cp, awk, grep, diff, tar, ps, date + local system + + # cp + for system in SunOS Darwin; do + [ "$SYSTEM_NAME" = "$system" ] && __define_and_eval__ 'export __legacy_cp__=1' + done + [ -n "$__legacy_cp__" ] && __define_and_eval__ 'function cp_a() { /bin/cp -pR "$@"; }' + + # awk + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ awk "gawk or nawk" \ + gawk \ + /usr/sfw/bin/gawk \ + /opt/sfw/bin/gawk \ + --legacy nawk \ + /usr/bin/nawk + + elif [ "$SYSTEM_NAME" = "Darwin" ]; then + # note: gensub() n'est supporté que sur gnuawk + # si __legacy_awk__ est défini, on sait que cette fonction n'est pas + # disponible + __set_prog_func__ awk "gawk or nawk" \ + gawk \ + /sw/bin/gawk \ + /sw/bin/awk \ + --legacy /usr/bin/awk + fi + + # grep + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ grep - \ + ggrep \ + /usr/sfw/bin/ggrep \ + /opt/sfw/bin/ggrep \ + --legacy /usr/bin/grep + fi + if [ -n "$__legacy_grep__" ]; then + __define_and_eval__ 'function quietgrep() { grep "$@" >&/dev/null; }' + fi + + # diff + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ diff - \ + gdiff \ + /usr/sfw/bin/gdiff \ + /opt/sfw/bin/gdiff \ + --legacy /usr/bin/diff + fi + if [ -n "$__legacy_diff__" ]; then + __define_and_eval__ 'function quietdiff() { diff "$@" >&/dev/null; }' + fi + + # tar + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ tar - \ + gtar \ + /usr/sfw/bin/gtar \ + /opt/sfw/bin/gtar \ + --legacy /usr/bin/tar + + elif [ "$SYSTEM_NAME" = "Darwin" ]; then + __set_prog_func__ tar - \ + gnutar gtar \ + /sw/bin/gtar \ + --legacy /usr/bin/tar + fi + + # ps + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ ps - \ + /usr/ucb/ps \ + --legacy /usr/bin/ps + __set_prog_func__ legacyps - \ + /usr/bin/ps + __define_and_eval__ 'function is_running() { test -n "$(legacyps -o pid -p "$1" | grep -v PID)"; }' + fi + if [ -n "$__legacy_ps__" ]; then + __define_and_eval__ 'function ps_all() { ps -ef; }' + fi + + # sleep + if [ "$SYSTEM_NAME" = "SunOS" -o "$SYSTEM_NAME" = "Darwin" ]; then + __define_and_eval__ 'function little_sleep() { n=1000; while [ $n -gt 0 ]; do n=$(($n - 1)); done; }' + fi + + # date + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ date - \ + gdate \ + /usr/sfw/bin/gdate \ + /opt/sfw/bin/gdate \ + --legacy /usr/bin/date + + elif [ "$SYSTEM_NAME" = "Darwin" ]; then + __set_prog_func__ date - \ + --legacy /bin/date + fi + + # readlink + if ! progexists readlink; then + __define_and_eval__ 'function readlink() { if [ -L "$1" ]; then /bin/ls -ld "$1" | awk '"'"'{print $11'"'"'}; fi; }' + fi + + # mktemp + if ! progexists mktemp; then + __define_and_eval__ 'function mktemp() { make_tempfile "$@"; }' + fi + + # curl/wget (pour dumpurl) + if ! progexists curl; then + if [ "$SYSTEM_NAME" = "SunOS" ]; then + if __set_prog_func__ curl "" /usr/local/bin/curl; then + __define_and_eval__ 'export __curl_EXISTS__=1' + fi + fi + fi + if ! progexists wget; then + if [ "$SYSTEM_NAME" = "SunOS" ]; then + if __set_prog_func__ wget "" /usr/sfw/bin/wget /opt/sfw/bin/wget; then + __define_and_eval__ 'export __wget_EXISTS__=1' + fi + fi + fi + + # is_root + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __define_and_eval__ 'function is_root() { id | quietgrep "uid=0"; }' + + elif [ "$SYSTEM_NAME" = "Cygwin" -o "$SYSTEM_NAME" = "Mingw" ]; then + # on assume que sur ces systèmes, l'utilisateur est maitre à bord. + __define_and_eval__ 'function is_root() { true; }' + fi + + # sedi + if [ "$SYSTEM_NAME" = "Darwin" ]; then + __define_and_eval__ 'function __1sedi() { + local script="$1" input="$2" + if sed -i.bak "$script" "$input"; then + /bin/rm -f "$input.bak" + fi +} +function sedi() { + local script="$1" input + shift + for input in "$@"; do + __1sedi "$script" "$input" + done +}' + elif [ "$SYSTEM_NAME" = "SunOS" ]; then + __define_and_eval__ 'function __1sedi() { + local script="$1" input="$2" + if sed "$script" "$input" >"$input.out.$$"; then + chmod "$(/bin/ls -l "$input" | awk '\''function oct(mod) { + value = 0 + if (substr(mod, 1, 1) == "r") value = value + 4 + if (substr(mod, 2, 1) == "w") value = value + 2 + if (substr(mod, 3, 1) == "x") value = value + 1 + return value +} +{ + print oct(substr($1, 2, 3)) oct(substr($1, 5, 3)) oct(substr($1, 8, 3)) +}'\'')" "$input.out.$$" + /bin/mv -f "$input.out.$$" "$input" + fi +} +function sedi() { + local script="$1" input + shift + for input in "$@"; do + __1sedi "$script" "$input" + done +}' + fi + + # parse_date + if [ "$SYSTEM_NAME" = "Darwin" ]; then + function __pd_isleap() { + # tester si l'année $1 est bissextile + [ $(($1 % 4)) -eq 0 -a \( $(($1 % 100)) -ne 0 -o $(($1 % 400)) -eq 0 \) ] + } + function __pd_fix_month() { + # soit $1 le nom d'une variable contenant une année, et $2 le nom d'une + # variable contenant un mois, normaliser les valeurs de ces variables, en + # ajustant si nécessaire les valeurs. + local __pdfm_y="${!1}" __pdfm_m="${!2}" + let __pdfm_m=$__pdfm_m-1 + while [ $__pdfm_m -gt 11 ]; do + let __pdfm_m=$__pdfm_m-12 + let __pdfm_y=$__pdfm_y+1 + done + while [ $__pdfm_m -lt 0 ]; do + let __pdfm_m=$__pdfm_m+12 + let __pdfm_y=$__pdfm_y-1 + done + let __pdfm_m=$__pdfm_m+1 + eval "$1=$__pdfm_y; $2=$__pdfm_m" + } + __PD_MONTHDAYS=(0 31 28 31 30 31 30 31 31 30 31 30 31) + function __pd_monthdays() { + # calculer le nombre de jours du mois $2 de l'année $1 + local y="$1" m="$2" mds + __pd_fix_month y m + mds="${__PD_MONTHDAYS[$m]}" + [ "$m" -eq 2 ] && __pd_isleap "$y" && let mds=$mds+1 + echo $mds + } + function __pd_fix_day() { + # soit $1 le nom d'une variable contenant une année, $2 le nom d'une + # variable contenant un mois et $3 le nom d'une variable contenant un jour, + # normaliser les valeurs de ces variables, en ajustant si nécessaire les + # valeurs. cette fonction assume que la valeur de $2 est déjà corrigée avec + # __pd_fix_month + local __pdfd_y="${!1}" __pdfd_m="${!2}" __pdfd_d="${!3}" __pdfd_mds + let __pdfd_d=$__pdfd_d-1 + let __pdfd_mds=$(__pd_monthdays $__pdfd_y $__pdfd_m) + while [ $__pdfd_d -gt $(($__pdfd_mds-1)) ]; do + let __pdfd_d=$__pdfd_d-$__pdfd_mds + let __pdfd_m=$__pdfd_m+1 + __pd_fix_month __pdfd_y __pdfd_m + let __pdfd_mds=$(__pd_monthdays $__pdfd_y $__pdfd_m) + done + while [ $__pdfd_d -lt 0 ]; do + let __pdfd_m=$__pdfd_m-1 + __pd_fix_month __pdfd_y __pdfd_m + let __pdfd_d=$__pdfd_d-$(__pd_monthdays $__pdfd_y $__pdfd_m) + done + let __pdfd_d=$__pdfd_d+1 + eval "$1=$__pdfd_y; $2=$__pdfd_m; $3=$__pdfd_d" + } + function __pd_fix_date() { + # soit $1 le nom d'une variable contenant une année, $2 le nom d'une + # variable contenant un mois et $3 le nom d'une variable contenant un jour, + # normaliser les valeurs de ces variables, en ajustant si nécessaire les + # valeurs. + local __pdf_y="${!1}" __pdf_m="${!2}" __pdf_d="${!3}" + } + function parse_date() { + local value="$1" type="${2:-date}" + local d m y + # date courante + eval "$(date +%d/%m/%Y | awk -F/ '{ + print "d=" $1 "; m=" $2 "; y=" $3 + }')" + if [ "${value#+}" != "$value" ]; then + # ajouter $1 jours + d="$(($d+${value#+}))" + else + # parser une nouvelle date, en complétant avec les informations de la + # date du jour + eval "$(<<<"$value" awk -F/ "BEGIN { + dn=$dn; mn=$mn; yn=$yn + }"' + { + d = $1 + 0; if (d < 1) d = dn + 0; + m = $2 + 0; if (m < 1) m = mn + 0; + if ($3 == "") y = yn + 0; + else { y = $3 + 0; if (y < 100) y = y + 2000; } + print "d=" d "; m=" m "; y=" y + }')" + fi + # ensuite corriger les champs si nécessaire + __pd_fix_month y m + __pd_fix_day y m d + # enfin formater la date selon les volontés de l'utilisateur + case "$type" in + d|date) + awk "BEGIN { d=$d; m=$m; y=$y; "'printf "%02i/%02i/%04i\n", d, m, y }' + ;; + l|ldap) + awk "BEGIN { d=$d; m=$m; y=$y; "'printf "%04i%02i%02i000000+0400\n", y, m, d }' + ;; + esac + } + fi + } + __get_tools_path_and_caps__ + unset -f __set_prog_func__ __get_tools_path_and_caps__ + + __define_and_eval__ "__SYSTEM_CAPS_COMPUTED__=1" + unset -f __define_and_eval__ +fi diff --git a/legacy/sysinc/usebash b/legacy/sysinc/usebash new file mode 100644 index 0000000..44c4f5b --- /dev/null +++ b/legacy/sysinc/usebash @@ -0,0 +1,20 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# usebash: requiere l'utilisation de bash pour lancer ce script +use_bash=1 +if [ -n "$BASH" ]; then + if [ `basename "$BASH"` != sh ]; then + use_bash= + fi +fi +if [ -n "$use_bash" ]; then + default_bash="`which bash 2>/dev/null`" + for bash in "$default_bash" /bin/bash /usr/bin/bash /usr/local/bin/bash; do + if [ -x "$bash" ]; then + exec "$bash" "$0" "$@" + fi + done + echo "error: Ce script necessite bash" + exit 1 +fi +unset use_bash diff --git a/legacy/sysinc/utools b/legacy/sysinc/utools new file mode 100644 index 0000000..3523a7c --- /dev/null +++ b/legacy/sysinc/utools @@ -0,0 +1,40 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Mettre en place l'environnement pour l'utilisation des fonctions de utools. La +# fonction utools_source_functions() est définie. + +# répertoire de base des outils utools +UTOOLS_BASEDIR="@@dest@@/legacy" + +# fonction die(). peut être remplacée par la version de sysinc/base si ce +# fichier est inclus. +function die() { test -n "$*" && echo "error: $@"; exit 1; } + +# Vérifier que UTOOLS_BASEDIR a été mis à jour (cas d'une distribution qui a été +# déployée) +if [ "$UTOOLS_BASEDIR" = "@@"destdir"@@" ]; then + die "Ce script a été construit avec un répertoire de utools non deployé." +fi + +# définir la fonction source_functions() pour charger les fonctions de utools à +# partir du bon répertoire +UTOOLS_FUNCTIONS="base system_caps functions" +function utools_source_functions() { + # sourcer un fichier de utools. le mot spécial ALL en première position + # signifie tous les fichiers de $UTOOLS_FUNCTIONS, pris dans sysinc/ + if [ "$1" = "ALL" ]; then + local file + for file in $UTOOLS_FUNCTIONS; do + source "$UTOOLS_BASEDIR/sysinc/$file" || die + done + shift + fi + while [ -n "$1" ]; do + if [ -f "$UTOOLS_BASEDIR/lib/${1}_functions.sh" ]; then + source "$UTOOLS_BASEDIR/lib/${1}_functions.sh" || die + else + source "$UTOOLS_BASEDIR/$1" || die + fi + shift + done +} diff --git a/legacy/twinc/TODO.html b/legacy/twinc/TODO.html new file mode 100644 index 0000000..797df58 --- /dev/null +++ b/legacy/twinc/TODO.html @@ -0,0 +1,4050 @@ + + + + + + +TiddlyWiki - a reusable non-linear personal web notebook + + + + + + + + +
+
+ + + +
+
+
+
+
+
<<calendar>>\n<<newTiddler>>\n----\n[[Releases]]\n<<newRelease>>\n----\n[[TODOs]]\n<<newTODO>>\n<<newBUG>>\n<<markDONE>>
+
Gestion des tâches
+
twinc
+
Ajouter une confirmation pour la suppression des tiddler\n\nComme le bouton "delete" est affiché sur le tiddler en mode wikified, il faudrait une confirmation pour éviter de supprimer par erreur un tiddler.\n\nFAIT le 21/10/2005
+
Faire la macro {{{<<todo text>>}}} qui affiche une case à cocher suivi de text et qui permet de rayer text quand on a fini\n\nexemples:\n\n<<todo "case non cochée (action non effectuée)">>\n<<todo "case cochée (action effectuée)" done="21/10/2005">>\n\nFAIT le 21/10/2005
+
Changer le tag todo pour utiliser des styles avec la fonction setStylesheet()\n
+
Changer la macro {{{<<todo>>}}} pour utiliser un formatter {{{[] todo}}} à la place\n\nexemples:\n\n[] case non cochée (action non effectuée)\n[x] case cochée (action effectuée)\n\nFAIT le 25/10/2005. La macro {{{<<todo>>}}} a été supprimée, puisqu'elle est trop "compliquée"
+
Afficher les TODOs non faits le plus récent en premier\n\nFAIT le 04/11/2005
+
une fonction pour classer les numéros de version, de façon à classer les releases correctement
+
+ + diff --git a/legacy/twinc/TODO.js b/legacy/twinc/TODO.js new file mode 100644 index 0000000..57eb274 --- /dev/null +++ b/legacy/twinc/TODO.js @@ -0,0 +1,440 @@ +// -*- coding: utf-8 -*- +//@require base.js + +/* convention de nommage: + + funcTodo: fonction qui agit sur ou retourne une instance de Tiddler + funcTODO: fonction qui agit sur ou affiche un élément visuel. +*/ + +////////////////////////////////////////////////// +// Gestion des TODOs + +function getAllTodos(types, expectedTags) { + // Lister les todos des types spécifiés ("TODO" ou "DONE"), et qui contiennent + // tous les tags de expectedTags. + if (!expectedTags) expectedTags = []; + + var tiddlersMatchingTags = function(tiddlers, exceptedTags, sortBy, reverse) { + // retourner les tiddlers de tiddlers qui contiennent tous les tags de expectedTags + var result = []; + for(var i = 0; i < tiddlers.length; i++) { + if (expectedTags.length) { + var allTagsOk = true; + for (var j = 0; j < expectedTags.length; j++) { + if (! tiddlers[i].tags.containsTag(expectedTags[j])) { + allTagsOk = false; + break; + } + } + if (!allTagsOk) continue; + } + + result.push(tiddlers[i]); + } + + if (!sortBy) sortBy = "title"; + result.sortBy(sortBy, reverse); + + return result; + } + + var result = []; + for (var i = 0; i < types.length; i++) { + var type = types[i]; + var reverse = false; + if (type.charAt(0) == "+") { + type = type.substr(1); + } else if (type.charAt(0) == "-") { + type = type.substr(1); + var reverse = true; + } + result = result.concat(tiddlersMatchingTags(store.getTaggedTiddlers(type), expectedTags, "title", reverse)); + } + + return result; +} + +function getTodoText(tiddler) { + // Retourner "[[todoTitle]] - firstLine" + if (!(tiddler instanceof Tiddler)) tiddler = store.tiddlers[tiddler]; + if (!(tiddler instanceof Tiddler)) return; + + var title = tiddler.title; + var text = "[[" + title + "]]"; + var line = Tiddlers.getFirstLine(tiddler); + if (line != "") text += " - " + line; + + return text; +} + +////////////////////////////////////////////////// +// Les macros pour gérer les TODOs + +// Créer un nouveau TODO +config.macros.newTODO = {label: "new TODO", prompt: "Create a new TODO tiddler", accessKey: "T"}; +config.macros.newTODO.handler = function(place, macroName, params) { + if (!readOnly) { + createTiddlyButton(place, this.label, this.prompt, this.onClick, null, null, this.accessKey); + } +} +config.macros.newTODO.onClick = function(e) { + e = ClickHandler.getEvent(e); + + var now = new Date(); + var todoname = now.formatString("TODO-YYYY0MM0DD"); + var title = todoname + "-" + (store.getTaggedTiddlers(todoname).length + 1); + + clearMessage(); + displayTiddler(null, title, 2, null, null, false, false); + var body = document.getElementById("editorBody" + title); + if (body) body.focus(); + + var tagsBox = document.getElementById("editorTags" + title); + if(tagsBox) { + var tags = tagsBox.value.splitTags(); + tags.removeTag("DONE").addTag("TODO").addTag(todoname); + tagsBox.value = tags.joinTags(); + } + + return ClickHandler.end(e); +} + +// Créer un nouveau bug +config.macros.newBUG = {label: "new BUG", prompt: "Create a new BUG tiddler", accessKey: "B"}; +config.macros.newBUG.handler = function(place, macroName, params) { + if (!readOnly) { + createTiddlyButton(place, this.label, this.prompt, this.onClick, null, null, this.accessKey); + } +} +config.macros.newBUG.onClick = function(e) { + e = ClickHandler.getEvent(e); + + var now = new Date(); + var bugname = now.formatString("BUG-YYYY0MM0DD"); + var title = bugname + "-" + (store.getTaggedTiddlers(bugname).length + 1); + + clearMessage(); + displayTiddler(null, title, 2, null, null, false, false); + var body = document.getElementById("editorBody" + title); + if (body) body.focus(); + + var tagsBox = document.getElementById("editorTags" + title); + if(tagsBox) { + var tags = tagsBox.value.splitTags(); + tags.removeTag("DONE").addTag("BUG").addTag(bugname); + tagsBox.value = tags.joinTags(); + } + + return ClickHandler.end(e); +} + +// marquer un TODO comme étant effectué +config.macros.markDONE = {label: "mark as DONE", prompt: "Mark a TODO/BUG tiddler as DONE", accessKey: "D"}; +config.macros.markDONE.handler = function(place, macroName, params) { + if (!readOnly) { + createTiddlyButton(place, this.label, this.prompt, this.onClick, null, null, this.accessKey); + } +} +config.macros.markDONE.onClick = function(e) { + e = ClickHandler.getEvent(e); + + if (currentTiddler) { + var title = currentTiddler; + + var tags = Tiddlers.valueOf(title).tags; + if (tags.containsTag("TODO") || tags.containsTag("BUG")) { + clearMessage(); + displayTiddler(null, title, 2, null, null, false, false); + + var tagsBox = document.getElementById("editorTags" + title); + if(tagsBox) { + var tags = tagsBox.value.splitTags(); + tags.removeTag("TODO").removeTag("BUG").addTag("DONE"); + tagsBox.value = tags.joinTags(); + + var body = document.getElementById("editorBody" + title); + var text = body.value; + if (text.length > 0 && text.charAt(text.length - 1) != "\n") text += "\n"; + text += "\nFAIT le " + new Date().formatString("0DD/0MM/YYYY"); + body.value = text; + body.focus(); + } + } + } + + return ClickHandler.end(e); +} + +// Afficher un lien vers un TODO et une description du TODO +config.macros.todoLink = {}; +config.macros.todoLink.handler = function(place, macroName, params) { + if (!params.length) return; + + wikify(getTodoText(params[0]), place); +} + +// Lister tous les TODOs, ceux à faire et ceux qui sont faits +config.macros.listTODOs = {}; +config.macros.listTODOs.handler = function(place, macroName, params) { + var todos = getAllTodos(["-TODO", "BUG", "DONE"], params); + if (todos.length) { + var list = document.createElement("ul"); + place.appendChild(list); + + for(var i = 0; i < todos.length; i++) { + item = document.createElement("li"); + list.appendChild(item); + + var element = item; + var tags = todos[i].tags; + if (tags.containsTag("DONE")) { + striked = createTiddlyElement(element, "strike"); + element = striked; + } + + wikify(getTodoText(todos[i]), element); + } + } else { + place.appendChild(document.createTextNode("Il n'y a pas de TODOs en cours")); + } +} +function refreshTODOs(hint) { + refreshTiddler("TODOs"); +} +config.notifyTiddlers.push({name: null, notify: refreshTODOs}); +config.shadowTiddlers.TODOs = "<>"; + +// Créer une nouvelle release +config.macros.newRelease = {label: "new release", prompt: "Create a new release", accessKey: "R"}; +config.macros.newRelease.handler = function(place, macroName, params) { + if (!readOnly) { + createTiddlyButton(place, this.label, this.prompt, this.onClick, null, null, this.accessKey); + } +} +config.macros.newRelease.onClick = function(e) { + e = ClickHandler.getEvent(e); + + var now = new Date(); + var releasename = now.formatString("Release-YYYY0MM0DD"); + var title = releasename; + var releases = store.getTaggedTiddlers(releasename); + if (releases.length > 0) { + title += "-" + releases.length; + } + + clearMessage(); + displayTiddler(null, title, 2, null, null, false, false); + var body = document.getElementById("editorBody" + title); + if (body && body.value == "") { + // ne créer le contenu que la première fois. + var text = "release du " + now.formatString("0DD/0MM/YYYY") + "\nversion: \n"; + + // lister tous les TODOs faits en cours + var todos = getAllTodos(["DONE"]); + for (var i = 0; i < todos.length; i++) { + if (i == 0) text += "\n"; + text += "* <>\n"; + } + + body.value = text; + body.focus(); + } + + var tagsBox = document.getElementById("editorTags" + title); + if(tagsBox) { + var tags = tagsBox.value.splitTags(); + tags.addTag("release").addTag(releasename); + tagsBox.value = tags.joinTags(); + } + + return ClickHandler.end(e); +} +TiddlyWiki.prototype.saveTiddler_patchedByTODO = TiddlyWiki.prototype.saveTiddler; +TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier, modified, tags) { + var tiddler = this.saveTiddler_patchedByTODO(title, newTitle, newBody, modifier, modified, tags); + + if (tiddler.tags.containsTag("release")) { + var todos = store.getTaggedTiddlers("DONE"); + for (var i = 0; i < todos.length; i++) { + var todo = todos[i]; + todo.tags.removeTag("DONE").addTag("RELEASED"); + + store.saveTiddler(todo.title, todo.title, todo.text, todo.modifier, undefined, todo.tags); + refreshTiddler(todos[i].title); + } + } + + return tiddler; +} + +function getVersionText(tiddler) { + var version = ""; + var tiddler = Tiddlers.valueOf(tiddler); + if (tiddler) { + var re_version = new RegExp("^version:(.*)$", "gm"); + var matched = re_version.exec(tiddler.text); + if (matched) { + version = matched[1].trim(); + } + } + return version; +} + +function getReleaseText(tiddler) { + // Retourner "[[versionText|releaseTitle]] - firstLine" + var text = ""; + var tiddler = Tiddlers.valueOf(tiddler); + if (tiddler) { + var title = tiddler.title; + var version = getVersionText(tiddler); + if (version != "") { + text = "[[" + version + "|" + title + "]]"; + } else { + text = "[[" + title + "]]"; + } + var line = Tiddlers.getFirstLine(tiddler); + if (line != "") text += " - " + line; + } + return text; +} + +config.macros.listReleases = {}; +config.macros.listReleases.handler = function(place, macroName, params) { + var releases = store.getTaggedTiddlers("release"); + releases.sortBy("title", true); + if (releases.length) { + var list = document.createElement("ul"); + place.appendChild(list); + + var len = releases.length; + if (len > 7) len = 7; + for(var i = 0; i < len; i++) { + item = document.createElement("li"); + list.appendChild(item); + + wikify(getReleaseText(releases[i]), item); + } + } else { + place.appendChild(document.createTextNode("Il n'y a pas de releases en cours")); + } +} +function refreshReleases(hint) { + refreshTiddler("Releases"); +} +config.notifyTiddlers.push({name: null, notify: refreshReleases}); +config.shadowTiddlers.Releases = "<>"; + +////////////////////////////////////////////////// +// Le formatter todo +// ce formatter affiche une case à cocher qu'il est possible de cocher pour signifier que l'action +// associée à la case à cocher est effectuée +// format: [] pour les actions non faites +// [x] pour les actions faites + +var lastTodoId; + +var displayTiddler_patchedByTodo = window.displayTiddler; +window.displayTiddler = function(src, title, state, highlightText, highlightCaseSensitive, animate, slowly) { + // réinitialiser le numéro du dernier todo à chaque affichage + lastTodoId = 0; + displayTiddler_patchedByTodo(src, title, state, highlightText, highlightCaseSensitive, animate, slowly); +} + +config.formatters.push({ + name: "todo", + match: "\\[\\]|\\[[xX\\*]\\]", + terminator: "\\n|\\|", + handler: function(w) { + var formatter = this; + var place = w.output; + + var tiddler = findContainingTiddler(place); + if (!tiddler) return; + + var title = tiddler.id.substr(7); + var todoId = lastTodoId++; + var cbId = "todo" + todoId + "@" + title; + var wrapperId = "todoWrapper" + todoId + "@:" + title; + + var matched = w.source.substr(w.matchStart, w.matchLength); + var done = matched != "[]"; + var onClick = function(e) { + e = ClickHandler.getEvent(e); + + if (!readOnly) { + // lire les infos sur le tiddler + var body = store.getTiddlerText(title); + var tags = Tiddlers.getTags(title); + + // trouver le texte [] qui correspond à todoId + var id = 0; + var re_macro = new RegExp(formatter.match, "mg"); + do { + var mo = re_macro.exec(body); + if (mo) { + if (id++ == todoId) break; + } + } while (mo); + + // changer la valeur + if (mo) { + var done = mo[0] == "[]"; + body = body.substr(0, mo.index) + (done? "[x]": "[]") + body.substr(mo.index + mo[0].length); + + lastTodoId = 0; + store.saveTiddler(title, title, body, config.options.txtUserName, new Date(), tags); + if (config.options.chkAutoSave) saveChanges(); + refreshTiddler(title); + } + } + + return ClickHandler.end(e); + }; + + var span = createTiddlyElement(place, "span"); + var cb = createTiddlyElement(span, "input"); + cb.type = "checkbox"; + cb.id = cbId; + cb.checked = done; + cb.onclick = onClick; + var label = createTiddlyElement(span, "label"); + label.setAttribute("for", cbId); + var wrapper = createTiddlyElement(label, "span", wrapperId); + if (done) { + wrapper.setAttribute("style", "text-decoration: line-through;"); + } + w.subWikify(wrapper, this.terminator); + w.nextMatch--; // ne pas manger le terminator + } +}); + +////////////////////////////////////////////////// +// L'interface par défaut + +config.shadowTiddlers.MainMenu = "\ +<>\n\ +<>\n\ +----\n\ +[[Releases]]\n\ +<>\n\ +----\n\ +[[TODOs]]\n\ +<>\n\ +<>\n\ +<>\ +"; +config.shadowTiddlers.DefaultTiddlers = "\ +Releases\n\ +TODOs\n\ +EnCours\ +"; +config.shadowTiddlers.SiteTitle = "TODO"; +config.shadowTiddlers.SiteSubtitle = "Gestion de tâches"; + +config.shadowTiddlers.KeyShortcuts += "\ +|Alt + T|Créer un nouveau TODO|\n\ +|Alt + B|Créer un nouveau BUG|\n\ +|Alt + D|Terminer un TODO|\n\ +|Alt + R|Créer une nouvelle release|\n\ +"; diff --git a/legacy/twinc/base.js b/legacy/twinc/base.js new file mode 100644 index 0000000..5cce6eb --- /dev/null +++ b/legacy/twinc/base.js @@ -0,0 +1,985 @@ +// -*- coding: utf-8 -*- + +////////////////////////////////////////////////// +// Outils divers et méthodes supplémentaires pour les classes de base + +// Array +Array.prototype.indexOfItem = function(item) { + for (var i = 0; i < this.length; i++) { + if (this[i] == item) return i; + } + return -1; +} + +Array.prototype.containsItem = function(item) { + return this.indexOfItem(item) != -1; +} + +Array.prototype.sortBy = function(field, reverse) { + if (field == undefined) field = "title"; + this.sort(function(a, b) { + if (a[field] == b[field]) { + return 0; + } else { + var result = a[field] < b[field]? -1 : +1; + if (reverse) result = -result; + return result; + } + }); +} + +// String +String.prototype.addText = function(item) { + var str = this; + if (str != "") str += " "; + str += item; + return str; +} + +String.prototype.expandVars = function(vars, recursive) { + var text = this; + + // remplacer les éléments de date + var now = vars && vars["now"]? Dat.valueOf(vars["now"]): new Date(); + text = now.expandVars(text); + + if (vars) { + // remplacer la valeurs des variables + var pos = 0; + var re_var = new RegExp("\\$(?:([a-zA-Z0-9_]+)|{([^}]+)})", "mg"); + do { + re_var.lastIndex = pos; + var mo = re_var.exec(text); + if (mo) { + if (mo[1]) { + var name = mo[1]; + var value = vars[name]? vars[name]: mo[0]; + } else { + var name = mo[2]; + try { + var value = vars.eval(name); + } catch(e) { + var value = e.toString(); + } + } + text = text.substr(0, mo.index) + value + text.substr(mo.index + mo[0].length); + pos = mo.index; + if (!recursive) pos += new String(value).length; + } + } while(mo); + } + + return text; +} + +function expandVars(template, vars, recursive) { + return Str.valueOf(template).expandVars(vars, recursive); +} + +String.prototype.firstLine = function() { + // obtenir la première ligne non vide d'une chaine + var text = this, line = ""; + var start = 0, pos; + do { + pos = text.indexOf("\n", start); + if (pos == -1) { + line = text.substr(start); + break; + } else { + line = text.substring(start, pos); + if (new RegExp("\\S").test(line)) { + break; + } + } + start = pos + 1; + } while (pos != -1); + + return line; +} + +// Date +Date.parseFr = function(text) { + // créer une date à partir d'une chaine de la forme dd/mm/yyyy + var re_date = new RegExp("([0-9]+)/([0-9]+)/([0-9]+)"); + var mo = re_date.exec(text); + if (mo) { + return new Date(parseInt(mo[3], 10), parseInt(mo[2], 10) - 1, parseInt(mo[1], 10)); + } else { + return new Date(text); + } +} + +var MMM = ["Jan", "Fév", "Mar", "Avr", "Mai", "Jun", "Jui", "Aoû", "Sep", "Oct", "Nov", "Déc"]; +var MMMM = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"]; +var D = ["L", "M", "M", "J", "V", "S", "D"]; +var DD = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]; +var DDD = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]; +Date.prototype.expandVars = function(text) { + text = text.replace(/%Y/g, this.getFullYear()); + text = text.replace(/%4m/g, MMMM[this.getMonth()]); + text = text.replace(/%3m/g, MMM[this.getMonth()]); + text = text.replace(/%0m/g, String.zeroPad(this.getMonth() + 1, 2)); + text = text.replace(/%m/g, this.getMonth()); + text = text.replace(/%3d/g, DDD[this.getDow()]); + text = text.replace(/%2d/g, DD[this.getDow()]); + text = text.replace(/%1d/g, D[this.getDow()]); + text = text.replace(/%0d/g, String.zeroPad(this.getDate(), 2)); + text = text.replace(/%d/g, this.getDate()); + text = text.replace(/%H/g, this.getHours()); + text = text.replace(/%M/g, this.getMinutes()); + text = text.replace(/%S/g, this.getSeconds()); + return text; +} + +Date.prototype.formatFr = function() { + return this.expandVars("%0d/%0m/%Y"); +} + +Date.prototype.formatFrLong = function() { + return this.expandVars("%3d %0d/%0m/%Y"); +} + +Date.prototype.getWom = function() { + // obtenir le numéro de la semaine du mois: 0 - 4 + var lundi = this.getMonday(); + var day = lundi.getDate(); + return Math.floor(day/7); +} + +Date.prototype.getDow = function() { + // obtenir le jour de la semaine: 0(lundi) - 6(dimanche) + var dow = this.getDay(); + dow = (dow + 6) % 7; + return dow; +} + +Date.prototype.getMidnight = function() { + // obtenir la même date à minuit + return new Date(this.getFullYear(), this.getMonth(), this.getDate()); +} + +Date.prototype.addMonths = function(nb) { + // ajouter nb mois à la date, et retourner la date résultante à minuit + var y = this.getFullYear(), m = this.getMonth(), d = this.getDate(); + m += nb; + while (m < 0) { + m += 12; + y--; + } + while (m > 11) { + m -= 12; + y++; + } + return new Date(y, m, d); +} + +Date.prototype.addDays = function(nb) { + // ajouter nb jours à la date, et retourner la date résultante à minuit + var y = this.getFullYear(), m = this.getMonth(), d = this.getDate(); + return new Date(y, m, d + nb); +} + +Date.prototype.diffDays = function(date) { + return (date.getMidnight() - this.getMidnight()) / 86400000; +} + +Date.prototype.getMonday = function() { + // obtenir le lundi de la semaine en cours + return this.addDays(-this.getDow()); +} + +Date.prototype.getFirstDay = function() { + // obtenir le premier jour du mois + return new Date(this.getFullYear(), this.getMonth(), 1); +} + +var NUM_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +Date.prototype.getNumDays = function() { + // obtenir le nombre de jours de ce mois + var m = this.getMonth(); + if (m == 1) { + var y = this.getFullYear(); + var isleap = (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0); + return NUM_DAYS[m] + (isleap? 1: 0); + } else { + return NUM_DAYS[m]; + } +} + +Date.prototype.getLastDay = function() { + // obtenir le dernier jour du mois + return new Date(this.getFullYear(), this.getMonth(), this.getNumDays()); +} + +// ClickHandler +var ClickHandler = {}; +ClickHandler.getEvent = function(e) { + if (!e) e = window.event; + return e; +} +ClickHandler.end = function(e) { + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + return false; +} + +// Str +var Str = {}; +Str.valueOf = function(s) { + if (typeof(s) == "string") s = new String(s); + if (!(s instanceof String)) { + if (s == null || s == undefined) s = ""; + s = new String(s); + } + return s; +} + +// Dat +var Dat = {}; +Dat.valueOf = function(d) { + if (d instanceof String || typeof(d) == "string") { + return Date.parseFr(d); + } else if (d instanceof Date) { + return d; + } else { + return new Date(d); + } +} + +// Bool +var Bool = {}; +Bool.valueOf = function(b) { + if (b instanceof String || typeof(b) == "string") { + var b = Str.valueOf(b).toLowerCase(); + return ["oui", "o", "yes", "y", "vrai", "v", "true", "t", "1"].containsItem(b); + } + return Boolean(b).valueOf(); +} + +////////////////////////////////////////////////// +// Fonctions pour gérer les tags + +Array.prototype.removeTag = function(tag) { + var pos = this.indexOfItem(tag); + if (pos != -1) { + this.splice(pos, 1); + } + return this; +} + +Array.prototype.addTag = function(tag) { + this.pushUnique(tag); + return this; +} + +Array.prototype.addTags = function(tags) { + if (tags instanceof String || typeof(tags) == "string") { + tags = new String(tags).splitTags(); + } + for (var i = 0; i < tags.length; i++) { + this.addTag(tags[i]); + } +} + +Array.prototype.containsTag = function(tag) { + return this.containsItem(tag); +} + +Array.prototype.joinTags = function() { + var result = []; + for (var i = 0; i < this.length; i++) { + result.push(String.encodeTiddlyLink(this[i])); + } + return result.join(" "); +} + +String.prototype.splitTags = function() { + return this.readBracketedList(true); +} + +String.prototype.addTag = function(tag) { + var thistags = this.splitTags(); + thistags.addTag(tag); + return thistags.joinTags(); +} + +String.prototype.addTags = function(tags) { + var thistags = this.splitTags(); + thistags.addTags(tags); + return thistags.joinTags(); +} + +////////////////////////////////////////////////// +// Gestion des tiddlers et patches divers + +// Tiddlers +var Tiddlers = {}; +Tiddlers.valueOf = function(tiddler) { + if (!(tiddler instanceof Tiddler)) { + tiddler = store.tiddlers[tiddler]; + } + return tiddler; +} + +Tiddlers.getTags = function(tiddler) { + // obtenir la valeur de tags pour un tiddler + var tiddler = this.valueOf(tiddler); + if (tiddler) return tiddler.getTags(); + return ""; +} + +Tiddlers.getSubtitle = function(tiddler) { + // obtenir la valeur de subtitle pour un title + var tiddler = this.valueOf(tiddler); + if (tiddler) return tiddler.getSubtitle(); + return config.messages.subtitleUnknown; +} + +Tiddlers.getFirstLine = function(tiddler) { + var tiddler = this.valueOf(tiddler); + if (tiddler) return tiddler.text.firstLine(); + return ""; +} + +// patches divers +var createTiddlyButton_patchedByBase = window.createTiddlyButton; +window.createTiddlyButton = function(parent, text, tooltip, action, klass, id, accessKey) { + // afficher la touche de raccourci dans le title du bouton + if (accessKey) { + if (!tooltip) tooltip = ""; + tooltip = Str.valueOf(tooltip).addText("(Alt + " + accessKey + ")"); + } + return createTiddlyButton_patchedByBase(parent, text, tooltip, action, klass, id, accessKey); +} + +closeTiddler_patchedByBase = window.closeTiddler; +window.closeTiddler = function(title, slowly) { + // patcher close pour désactiver l'animation sur la fermeture + var saveAnimate = config.options.chkAnimate; + config.options.chkAnimate = false; + closeTiddler_patchedByBase(title, slowly); + config.options.chkAnimate = saveAnimate; +} + +displayTiddlers_patchedByBase = window.displayTiddlers; +window.displayTiddlers = function(src, titles, state, highlightText, highlightCaseSensitive, animate, slowly) { + // patcher displayTiddlers pour permettre l'affichage d'une liste de tiddlers générés par une macro. + // La macro doit implémenter textHandler pour être reconnue + if (titles.indexOf("<<") != -1) { + var pos = 0; + var re_macro = new RegExp("<<([^>\\s]+)(?:\\s*)([^>]*)>>", "mg"); + do { + re_macro.lastIndex = pos; + var mo = re_macro.exec(titles); + if (mo) { + var macro = config.macros[mo[1]]; + if (macro && macro.textHandler) { + var params = mo[2].readMacroParams(); + var text = macro.textHandler(params); + titles = titles.substr(0, mo.index) + text + titles.substr(mo.index + mo[0].length); + pos = mo.index + new String(text).length; + } else { + pos = mo.index + mo[0].length; + } + } + } while (mo); + titles = titles.replace(/<<([^>\s]+)(?:\s*)([^>]*)>>/g, ""); + } + displayTiddlers_patchedByBase(src, titles, state, highlightText,highlightCaseSensitive,animate,slowly); +} + +// patcher createTiddlerToolbar pour que la liste des boutons à afficher soit dans un tableau modifiable +config.views.wikified.toolbarClose.onClick = onClickToolbarClose; +config.views.wikified.toolbarEdit.onClick = onClickToolbarEdit; +config.views.wikified.toolbarEdit.notReadOnly = true; +config.views.wikified.toolbarPermalink.onClick = onClickToolbarPermaLink; +config.views.wikified.toolbarReferences.onClick = onClickToolbarReferences; +config.views.wikified.toolbarButtons = [ + config.views.wikified.toolbarClose, + config.views.wikified.toolbarEdit, + config.views.wikified.toolbarPermalink, + config.views.wikified.toolbarReferences]; + +config.views.editor.toolbarDone.onClick = onClickToolbarSave; +config.views.editor.toolbarDone.notReadOnly = true; +config.views.editor.toolbarCancel.onClick = onClickToolbarUndo; +config.views.editor.toolbarDelete.onClick = onClickToolbarDelete; +config.views.editor.toolbarDelete.notReadOnly = true; +config.views.editor.toolbarButtons = [ + config.views.editor.toolbarDone, + config.views.editor.toolbarCancel, + config.views.editor.toolbarDelete]; + +// Create a tiddler toolbar according to whether it's an editor or not +createTiddlerToolbar = function(title, isEditor) { + var theToolbar = document.getElementById("toolbar" + title); + if(theToolbar) { + var lingo = config.views; + if (isEditor) lingo = lingo.editor; + else lingo = lingo.wikified; + + removeChildren(theToolbar); + insertSpacer(theToolbar); + var toolbarButtons = lingo.toolbarButtons; + for (var i = 0; i < toolbarButtons.length; i++) { + var toolbarButton = toolbarButtons[i]; + + if (i > 0) insertSpacer(theToolbar); + if (!readOnly || !toolbarButton.notReadOnly) { + createTiddlyButton(theToolbar, toolbarButton.text, toolbarButton.tooltip, toolbarButton.onClick); + } + } + } +} + +function installToolbarButton(toolbarButton, editor, before) { + // installer un bouton dans la vue wiki (par défaut) ou dans la vue éditeur (si editor==true), avant le bouton before + var lingo = config.views; + if (editor) lingo = lingo.editor; + else lingo = lingo.wikified; + + var pos = -1; + if (before) { + pos = lingo.toolbarButtons.indexOfItem(before); + } + if (pos == -1) { + lingo.toolbarButtons.push(toolbarButton); + } else { + lingo.toolbarButtons.splice(pos, 0, toolbarButton); + } +} + +function removeToolbarButton(toolbarButton, editor) { + var lingo = config.views; + if (editor) lingo = lingo.editor; + else lingo = lingo.wikified; + + var pos = lingo.toolbarButtons.indexOfItem(toolbarButton); + if (pos != -1) { + lingo.toolbarButtons.splice(pos, 1); + } +} + +////////////////////////////////////////////////// +// Gestion des paramètres + +// Process a string list of macro parameters into an array. Parameters can be quoted +// with "", '', [[]] or left unquoted (and therefore space-separated) +// On reconnait aussi les paramètres de la forme name="value", name='value' ou name=value +String.prototype.readMacroParams = function() { + var regexpMacroParam = new RegExp("(?:\\s*)(?:" + + "(?:\"([^\"]*)\")|" + + "(?:'([^']*)')|" + + "(?:\\[\\[([^\\]]*)\\]\\])|" + + "([^\"'\\s]\\S*=(?:[^\"'\\s]\\S*|\"[^\"]*\"|'[^']*'))|" + + "([^\"'\\s]\\S*)" + + ")","mg"); + var params = []; + do { + var match = regexpMacroParam.exec(this); + if(match) { + if(match[1]) params.push(match[1]); // Double quoted + else if(match[2]) params.push(match[2]); // Single quoted + else if(match[3]) params.push(match[3]); // Double-square-bracket quoted + else if(match[4]) params.push(match[4]); // name=value, name="value" ou name='value' + else if(match[5]) params.push(match[5]); // Unquoted + } + } while(match); + return params; +} + +function Options(params) { + // Dans un tableau de paramètre construit par String.readMacroParams, reconnaitre les options de la forme name=value + // L'objet retourné contient le tableau arg qui est une copie de params sans les options, et une propriété pour chaque option. + + this.args = []; + var re_option = new RegExp("([^\"'\\s]\\S*)=(?:([^\"'\\s]\\S*)|\"([^\"]*)\"|'([^']*)')"); + for (var i = 0; i < params.length; i++) { + var mo = re_option.exec(params[i]); + if (mo) { + var name = mo[1]; + var value = mo[2]? mo[2]: mo[3]? mo[3]: mo[4]? mo[4]: ""; + this[name] = value; + } else { + this.args.push(params[i]); + } + } +} +Options.prototype.get = function(name, def) { + // obtenir la valeur d'une option, avec une valeur par défaut + if (this[name]) return this[name]; + return def; +} +Options.prototype.getArg = function(index, def) { + // obtenir la valeur d'un argument, avec une valeur par défaut + if (this.args[index]) return this.args[index]; + return def; +} +Options.prototype.toParams = function() { + var params = ""; + for (var i = 0; i < this.args.length; i++) { + var arg = this.args[i]; + if (new RegExp("\\s").test(arg)) { + params = params + " \"" + arg + "\""; + } else { + params = params + " " + arg; + } + } + for (var prop in this) { + if (prop != "args" && prop != "get" && prop != "getArg" && prop != "toParams") { + params = params + " " + prop + "="; + + var arg = new String(this[prop]); + if (new RegExp("\\s").test(arg)) { + params = params + "\"" + arg + "\""; + } else { + params = params + arg; + } + } + } + return params; +} + +////////////////////////////////////////////////// +// La macro newTiddler +// arguments: title text= tooltip= tags= + +config.macros.newTiddler.handler = function(place, macroName, params) { + // créer un nouveau tiddler avec le nom params[0] et les tags params[1..n] + if(!readOnly) { + var options = new Options(params); + var legacy = options.args[0]? false: true; + var tags = expandVars(options.get("tags", "")); + if (legacy) { + var title = config.macros.newTiddler.title; + var tooltip = options.get("tooltip", config.macros.newTiddler.prompt); + var text = options.get("text", config.macros.newTiddler.label); + } else { + var title = expandVars(options.args[0]); + var tooltip = options.get("tooltip", "Create a new tiddler named " + title); + var text = options.get("text", "new " + (tags? "tagged ": "") + "tiddler"); + } + + var onClick = function() { + displayTiddler(null, title, 2, null, null, false, false); + var tagsBox = document.getElementById("editorTags" + title); + if(tagsBox) tagsBox.value = tagsBox.value.addTags(tags); + if (legacy) { + var e = document.getElementById("editorTitle" + title); + e.focus(); + e.select(); + } + return false; + } + createTiddlyButton(place, text, tooltip, onClick, undefined, undefined, legacy? this.accessKey: undefined); + } +} + +////////////////////////////////////////////////// +// Les macros de gestion d'événements + +// La macro event +// arguments: date title nbDays= format= +config.macros.event = { + TODAY: "aujourd'hui", + TOMORROW: "demain", + YESTERDAY: "hier", + FUTURE_EVENT: "dans $remaining jours", + PAST_EVENT: "il y a ${-remaining} jours", + FORMAT: "$remainingDays: $title le $date" +}; +config.macros.event.getRemainingDays = function(remaining) { + if (remaining == 0) remainingDays = this.TODAY; + else if (remaining == 1) remainingDays = this.TOMORROW; + else if (remaining == -1) remainingDays = this.YESTERDAY; + else if (remaining < 0) remainingDays = this.PAST_EVENT; + else remainingDays = this.FUTURE_EVENT; + + return remainingDays; +} +config.macros.event.textHandler = function(params) { + var options = new Options(params); + var date = options.args[0]? Date.parseFr(options.args[0]): new Date(); + var title = options.args[1]? options.args[1]: "Unnamed event"; + var nbDays = parseInt(options.get("nbDays", "15"), 10); + var format = options.get("format", this.FORMAT); + + var remaining = new Date().diffDays(date); + var remainingDays = this.getRemainingDays(remaining); + + var vars = {remaining: remaining, remainingDays: remainingDays, title: title, date: date.formatFr()}; + if (Math.abs(remaining) <= nbDays) return expandVars(format, vars, true); +} +config.macros.event.handler = function(place, macroName, params) { + var text = this.textHandler(params); + if (text && text != "") wikify(text, place); +} + +// La macro showEvents +// arguments: nbDays= format= headerFormat= noEventsFormat= +config.macros.showEvents = { + NO_EVENTS_FORMAT: "Il n'y a pas d'événements en cours", + HEADER_FORMAT: "|Date|Evénement|", + FORMAT: "|$date|$title|" +}; +config.macros.showEvents.textHandler = function(params) { + var options = new Options(params); + var noEventsFormat = options.get("noEventsFormat", this.NO_EVENTS_FORMAT); + var headerFormat = options.get("headerFormat", this.HEADER_FORMAT); + var format = options.get("format", this.FORMAT); + + var vars = {} + var tiddlers = store.reverseLookup("tags", "event", true, "title"); + + if (tiddlers && tiddlers.length) { + var text = expandVars(headerFormat, vars) + "\n"; + + for (var i = 0; i < tiddlers.length; i++) { + var tags = tiddlers[i].getTags(); + var re_date = new RegExp("\\s?@([0-9]+/[0-9]+/[0-9]+)\\s?", "g"); + var mo = re_date.exec(tags); + if (mo) { + var date = Date.parseFr(mo[1]); + var title = tiddlers[i].title; + var remaining = new Date().diffDays(date); + var remainingDays = config.macros.event.getRemainingDays(remaining); + + var vars = {remaining: remaining, remainingDays: remainingDays, title: title, date: date.formatFr()}; + text += expandVars(format, vars, true) + "\n"; + } + } + } else { + var text = expandVars(noEventsFormat, vars); + } + + return text; +} +config.macros.showEvents.handler = function(place, macroName, params) { + var text = this.textHandler(params); + if (text && text != "") wikify(text, place); +} + +// La macro calendar +// arguments: +var currentFirstDay; + +config.macros.calendar = {}; +config.macros.calendar.refresh = function(e) { + var target = resolveTarget(ClickHandler.getEvent(e)); + var tiddler = findContainingTiddler(target); + if (tiddler) { + refreshTiddler(tiddler.id.substr(7)); + } else { + while(target && target.id != "mainMenu") { + target = target.parentNode; + } + if (target) { + refreshMenu(); + } + } +} +config.macros.calendar.handler = function(place, macroName, params) { + var now = new Date().getMidnight(); + if (currentFirstDay) { + var firstDay = currentFirstDay; + } else { + var firstDay = now.getFirstDay(); + } + var firstCalDay = firstDay.getMonday(); + + var table, tr, td; + + buildModifiedTiddlers(); + + table = createTiddlyElement(place, "table"); + table.setAttribute("class", "calendar"); + + var onClickPreviousMonth = function(e) { + currentFirstDay = currentFirstDay.addMonths(-1); + config.macros.calendar.refresh(e); + return false; + } + + var onClickNextMonth = function(e) { + currentFirstDay = currentFirstDay.addMonths(1); + config.macros.calendar.refresh(e); + return false; + } + + tr = createTiddlyElement(table, "tr"); + tr.setAttribute("class", "navRow"); + + td = createTiddlyElement(tr, "td"); + createTiddlyButton(td, "<", "mois précédent", onClickPreviousMonth); + + td = createTiddlyElement(tr, "td"); + td.colSpan = 5; + createTiddlyText(td, MMMM[firstDay.getMonth()] + " " + firstDay.getFullYear()); + + td = createTiddlyElement(tr, "td"); + createTiddlyButton(td, ">", "mois suivant", onClickNextMonth); + + tr = createTiddlyElement(table, "tr"); + tr.setAttribute("class", "hdrRow"); + + for (var i = 0; i < D.length; i++) { + td = createTiddlyElement(tr, "td"); + createTiddlyText(td, D[i]); + } + + createCal(table, now, firstCalDay, firstDay); + currentFirstDay = firstDay; +} + +function createCal(table, now, firstCalDay, firstDay) { + var td, tr; + + var lastDay = firstDay.getLastDay(); + var day = firstCalDay; + + do { + tr = createTiddlyElement(table, "tr"); + tr.setAttribute("class", "calRow"); + for (var i = 0; i < 7; i++) { + td = createTiddlyElement(tr, "td"); + createCalDay(td, day, firstDay, now, lastDay); + day = day.addDays(1); + } + } while (day <= lastDay); +} + +function createCalDay(td, day, firstDay, now, lastDay) { + var modified = getModifiedTiddlers(day); + var events = getEvents(day); + var title = day.formatFr(); + + var klass = new String(); + if (day.getTime() == now.getTime()) klass = klass.addText("calToday"); + if (day < firstDay || day > lastDay) klass = klass.addText("calOther"); + if ((modified && modified.length) ||(events && events.length)) { + klass = klass.addText("calEvent"); + var a = []; + if (modified && modified.length) a.push("" + modified.length + " tiddler(s)"); + if (events && events.length) a.push("" + events.length + " événements(s)"); + title += ": " + a.join(","); + } + td.setAttribute("class", klass); + + td.title = title; + td.onclick = function(e) { + e = ClickHandler.getEvent(e); + var popup = Popup.create(resolveTarget(e)); + + if (!readOnly) { + var tags = "event @" + day.formatFr(); + wikify("<>", popup); + } + + var addLinks = function(tiddlers, title, field) { + if (tiddlers && tiddlers.length) { + if (field) tiddlers.sortBy(field); + var div = createTiddlyElement(popup, "div", null, null, title); + for (var i = 0; i < tiddlers.length; i++) { + var div = createTiddlyElement(popup, "div"); + var link = createTiddlyLink(div, tiddlers[i].title, false); + insertSpacer(link); + insertSpacer(link); + createTiddlyText(link, tiddlers[i].title); + } + } + } + + addLinks(events, "événements:"); + addLinks(modified, "modifiés:", "title"); + + Popup.show(); + return ClickHandler.end(e); + } + + createTiddlyText(td, day.getDate()); +} + +var modifiedTiddlers; + +function buildModifiedTiddlers() { + modifiedTiddlers = {}; + for (var t in store.tiddlers) { + var tiddler = store.tiddlers[t]; + var date = tiddler.modified.formatFr(); + if (!modifiedTiddlers[date]) { + modifiedTiddlers[date] = []; + } + modifiedTiddlers[date].push(tiddler); + } +} + +function getModifiedTiddlers(date) { + return modifiedTiddlers[date.formatFr()]; +} + +function getEvents(date) { + return store.reverseLookup("tags", "@" + date.formatFr(), true, "modified"); +} + +setStylesheet("\ +/** Le calendrier se divise en trois parties: nav (pour la navigation),\ +hdr (pour les jours de la semaine), et cal (pour les jours du calendrier proprement dit)\ +\ +Chaque style est préfixé de nav, hdr ou cal.\ +*/\ +.calendar {\ + margin: 0em;\ + text-align: center;\ + font-size: 9pt;\ +}\ +\ +/** la barre de navigation */\ +.navRow {\ +}\ +\ +/** la ligne d'en-tête, qui contient les jours */\ +.hdrRow {\ +}\ +\ +/** une ligne du calendrier */\ +.calRow {\ +}\ +\ +.calToday { /* cellule de calendrier: date du jour */\ + color: Blue;\ + border: 1px solid black;\ +}\ +\ +.calOther { /* cellule de calendrier: jour d'un autre mois */\ + color: LightSteelBlue;\ + font-size: 8pt;\ +}\ +\ +.calEvent { /* cellule de calendrier: événement disponible */\ + font-weight: bold;\ + text-decoration: underline;\ +}\ +", "calendar"); + +////////////////////////////////////////////////// +// Gestion de l'autorefresh +function refreshAllTiddlersTaggedAutoRefresh(hint) { + // rafraichir tous les tiddlers ayant le tag "autoRefresh" + var tagged = store.getTaggedTiddlers("autoRefresh"); + for (var i = 0; i < tagged.length; i++) { + refreshTiddler(tagged[i].title); + } +} +config.notifyTiddlers.push({name: null, notify: refreshAllTiddlersTaggedAutoRefresh}); + +////////////////////////////////////////////////// +// Le bouton keepThis + +function keepTiddler(titleToKeep) { + var display = document.getElementById("tiddlerDisplay"); + var tiddler = display.firstChild; + while(tiddler) { + var nextTiddler = tiddler.nextSibling; + if(tiddler.id) { + if(tiddler.id.substr(0,7) == "tiddler") { + var title = tiddler.id.substr(7); + if(titleToKeep != title && !document.getElementById("editorWrapper" + title)) + display.removeChild(tiddler); + } + } + tiddler = nextTiddler; + } +} + +var toolbarKeep = {text: "keep this", tooltip: "Close all other tiddlers"}; +toolbarKeep.onClick = function(e) { + e = ClickHandler.getEvent(e); + + if(this.parentNode.id) { + keepTiddler(this.parentNode.id.substr(7)); + } + + return ClickHandler.end(e); +} + +////////////////////////////////////////////////// +// Les macros closeCurrent, editCurrent, saveCurrent + +config.macros.closeCurrent = {label: "close", prompt: "Close current tiddler", accessKey: "W"}; +config.macros.closeCurrent.handler = function(place, macroName, params) { + createTiddlyButton(place, this.label, this.prompt, this.onClick, null, null, this.accessKey); +} +config.macros.closeCurrent.onClick = function(e) { + e = ClickHandler.getEvent(e); + + if (currentTiddler) { + // ne pas fermer un tiddler s'il est en cours d'édition + if(!document.getElementById("editorWrapper" + currentTiddler)) { + clearMessage(); + closeTiddler(currentTiddler, e.shiftKey || e.altKey); + currentTiddler = null; + } + } + + return ClickHandler.end(e); +} + +config.macros.editCurrent = {label: "edit", prompt: "Edit current tiddler", accessKey: "E"}; +config.macros.editCurrent.handler = function(place, macroName, params) { + if (!readOnly) { + createTiddlyButton(place, this.label, this.prompt, this.onClick, null, null, this.accessKey); + } +} +config.macros.editCurrent.onClick = function(e) { + e = ClickHandler.getEvent(e); + + if (currentTiddler) { + clearMessage(); + displayTiddler(null, currentTiddler, 2, null, null, false, false); + } + + return ClickHandler.end(e); +} + +config.macros.saveCurrent = {label: "save", prompt: "Save current tiddler", accessKey: "S"}; +config.macros.saveCurrent.handler = function(place, macroName, params) { + if (!readOnly) { + createTiddlyButton(place, this.label, this.prompt, this.onClick, null, null, this.accessKey); + } +} +config.macros.saveCurrent.onClick = function(e) { + e = ClickHandler.getEvent(e); + + if (currentTiddler) { + clearMessage(); + saveTiddler(currentTiddler, e.shiftKey); + } + + return ClickHandler.end(e); +} + +// patcher les méthodes existantes pour supporter la notion de "current tiddler" +var currentTiddler = null; + +var selectTiddler_patchedByBase = window.selectTiddler; +window.selectTiddler = function(title) { + // un tiddler sélectionné avec la souris devient le tiddler courant + selectTiddler_patchedByBase(title); + currentTiddler = title; +} + +var displayTiddler_patchedByBase = window.displayTiddler; +window.displayTiddler = function(src, title, state, highlightText, highlightCaseSensitive, animate, slowly) { + displayTiddler_patchedByBase(src, title, state, highlightText, highlightCaseSensitive, animate, slowly); + // un tiddler que l'on affiche devient le tiddler courant + currentTiddler = title; +} + +var deleteTiddler_patchedByBase = window.deleteTiddler; +window.deleteTiddler = function(title) { + deleteTiddler_patchedByBase(title); + if (title == currentTiddler) { + // si on supprimer le tiddler courant, il n'y a plus de tiddler courant + currentTiddler = null; + } +} diff --git a/legacy/twinc/baseui.js b/legacy/twinc/baseui.js new file mode 100644 index 0000000..0def77c --- /dev/null +++ b/legacy/twinc/baseui.js @@ -0,0 +1,210 @@ +// -*- coding: utf-8 -*- +//@require base.js + +// options par défaut +config.options.chkAnimate = true; +config.options.txtUserName = "JephteCLAIN"; +config.options.chkSaveBackups = false; +config.options.chkAutoSave = true; +config.options.chkHttpReadOnly = true; + +config.views.editor.defaultText = ""; + +delete config.macros.saveChanges.accessKey; +delete config.macros.newJournal.accessKey; +config.macros.timeline.dateFormat = "0DD/0MM/YYYY"; + +// installer Les macros closeCurrent, keepCurrent, editCurrent, saveCurrent dans la barre latérale +var sbo = config.shadowTiddlers.SideBarOptions; +sbo = sbo.replace("<>", "<><>"); +sbo = sbo.replace("<>", "<><><>"); +config.shadowTiddlers.SideBarOptions = sbo; +delete sbo; + +// modifier les boutons affichés par défaut +installToolbarButton(toolbarKeep, false, config.views.wikified.toolbarClose); +removeToolbarButton(config.views.wikified.toolbarPermalink); + +// déplacer le bouton delete, et ajouter une confirmation sur son utilisation +removeToolbarButton(config.views.editor.toolbarDelete, true); +installToolbarButton(config.views.editor.toolbarDelete); +window.onClickToolbarDelete = function(e) { + if(this.parentNode.id) { + var title = this.parentNode.id.substr(7); + if (confirm("Voulez-vous vraiment supprimer " + title + "?")) { + clearMessage(); + deleteTiddler(title); + } + } + return false; +} +config.views.editor.toolbarDelete.onClick = window.onClickToolbarDelete; + +// Désactiver le double-click +var createTiddlerSkeleton_patchedByBaseUi = window.createTiddlerSkeleton; +window.createTiddlerSkeleton = function(place, before, title) { + var tiddler = createTiddlerSkeleton_patchedByBaseUi(place, before, title); + delete tiddler.ondblclick; + return tiddler; +} +config.views.wikified.defaultText = "The tiddler '%0' doesn't yet exist."; + +// interface utilisateur par défaut +config.shadowTiddlers.OptionsPanel += "\ +, FormattingInstructions\ +, KeyShortcuts\ +, BaseConfiguration\ +"; + +config.shadowTiddlers.BaseConfiguration = "\ +! Configuration de ce wiki\n\ +Modifiez la valeur de ces tiddlers pour configurer ce wiki:\n\ +* SiteTitle - titre du wiki\n\ +* SiteSubtitle - sous-title du wiki\n\ +* SiteUrl - url du site web pour la génération du feed rss\n\ +* MainMenu - contenu du menu principal\n\ +* DefaultTiddlers - tiddlers affichés par défaut\n\ +* StyleSheet - feuille de style utilisée\n\ +"; +config.shadowTiddlers.FormattingInstructions = "\ +!! Syntaxes de base\n\ +|Utiliser cette syntaxe:|Pour obtenir ceci:|h\n\ +|{{{''Bold''}}}|''Bold''|\n\ +|{{{==Striked==}}}|==Striked==|\n\ +|{{{__Underline__}}}|__Underline__|\n\ +|{{{//Italic//}}}|//Italic//|\n\ +|{{{Normal^^super^^}}}|Normal^^super^^|\n\ +|{{{Normal~~sub~~}}}|Normal~~sub~~|\n\ +|{{{@@Highlight@@}}}|@@Highlight@@|\n\ +|{{{@@color: red;background-color: black; texte rouge sur noir@@}}}|@@color: red;background-color: black; texte rouge sur noir@@ (application de styles css quelconques)|\n\ +|{{{~NotAWikiWord}}}|~NotAWikiWorkd|\n\ +|{{{[[Force wiki word]]}}}|[[Force wiki word]]|\n\ +|{{{[[rename wiki link|AWikiLink]]}}}|[[rename wiki link|AWikiLink]]|\n\ +|{{{[[external url|http://www.site.com/]]}}}|[[external url|http://www.site.com/]]|\n\ +Pour faire une séparation, utiliser 4 tirets '-':\n\ +----\n\ +3 '{' permettent de monospacer un texte comme {{{ceci}}} ou:\n\ +{{{\n\ +ceci\n\ +sur plusieurs lignes\n\ +}}}\n\ +\n\ +!header1\n\ +{{{!header1}}}\n\ +!!header2\n\ +{{{!!header2}}}\n\ +!!!header3\n\ +{{{!!!header3}}}\n\ +!!!!header4\n\ +{{{!!!!header4}}}\n\ +!!!!!header5\n\ +{{{!!!!!header5}}}\n\ +\n\ +!! Listes normales\n\ +* dotted list\n\ +* two\n\ +* three\n\ +** sublist\n\ +** sub1\n\ +** sub2\n\ +* four\n\ +\n\ +!! Listes numérotées\n\ +# numbered list\n\ +# two\n\ +# three\n\ +## sub0\n\ +## sub1\n\ +## sub2\n\ +# four\n\ +\n\ +!! Blockquotes\n\ +<<<\n\ +Sur plusieurs\n\ +lignes\n\ +<<<\n\ +Ou sur plusieurs niveaux:\n\ +>level1\n\ +>level1\n\ +>>level2\n\ +>>level2\n\ +>>>level3\n\ +>>level2\n\ +>level1\n\ +>level1\n\ +\n\ +!! Tables\n\ +Utiliser cette syntaxe:\n\ +{{{\n\ +|caption|c\n\ +|!header|!header|h\n\ +|cell|cell|\n\ +|>|colspan|\n\ +|rowspan|one|\n\ +|~|two|\n\ +|left| right|\n\ +|>| center |\n\ +}}}\n\ +Pour obtenir ceci:\n\ +|caption|c\n\ +|!header|!header|h\n\ +|cell|cell|\n\ +|>|colspan|\n\ +|rowspan|one|\n\ +|~|two|\n\ +|left| right|\n\ +|>| center |\n\ +\n\ +!! Insertion d'images\n\ +{{{\n\ +[img[title|filename]]\n\ +[img[filename]]\n\ +[img[title|filename][link]]\n\ +[img[filename][link]]\n\ +[img[filename]] (image cleared to right)\n\ +}}}\n\ +\n\ +"; + +config.shadowTiddlers.KeyShortcuts = "\ +|!Touche|!Action|h\n\ +|Alt + F|Chercher|\n\ +|Alt + W|Fermer le tiddler courant|\n\ +|Alt + E|Editer le tiddler courant|\n\ +|Alt + S|Valider les changements sur le tiddler courant|\n\ +"; + +config.shadowTiddlers.StyleSheet = "\ +#titleLine { padding: 1em 1em 1em 1em; }\n\ +#sidebar { left: 0em; }\n\ +#mainMenu { position: relative; width: auto; padding-top: 0.5em; }\n\ +#sidebarTabs .tabContents { width: auto; }\n\ +#displayArea { margin: 0em 0.3em 0em 16.3em; font-size: 8pt; }\n\ +\n\ +body { background-color: black; }\n\ +#mainMenu { background-color: white; }\n\ +.tiddler { background-color: white; margin-bottom: 0.3em; border-right: 3px solid #aaa; border-bottom: 3px solid #555; }\n\ +.toolbar .button { color: #000; border: 1px outset #cf6; background: #cf6; }\n\ +\n\ +#titleLine { color: #cf6; background-color: black; }\n\ +#siteSubtitle { color: white; }\n\ +"; + +var main_patchedByBaseUi = window.main; +window.main = function() { + var mainMenu = document.getElementById("mainMenu"); + var contentWrapper = document.getElementById("contentWrapper"); + contentWrapper.removeChild(mainMenu); + + var sidebar = document.getElementById("sidebar"); + var sidebarOptions = document.getElementById("sidebarOptions"); + sidebar.insertBefore(mainMenu, sidebarOptions); + + var messageArea = document.getElementById("messageArea"); + var displayArea = document.getElementById("displayArea"); + displayArea.removeChild(messageArea); + displayArea.appendChild(messageArea); + + return main_patchedByBaseUi(); +} diff --git a/legacy/twinc/old.js b/legacy/twinc/old.js new file mode 100644 index 0000000..06b7600 --- /dev/null +++ b/legacy/twinc/old.js @@ -0,0 +1,87 @@ +// -*- coding: utf-8 -*- + +////////////////////////////////////////////////// +// La macro todo +// cette macro affiche une case à cocher qu'il est possible de cocher pour signifier que l'action +// associée à la case à cocher est effectuée +// arguments: text done= dispdate= + +var lastTodoId; + +var displayTiddler_patchedByTodo = window.displayTiddler; +window.displayTiddler = function(src, title, state, highlightText, highlightCaseSensitive, animate, slowly) { + // réinitialiser le numéro du dernier todo à chaque affichage + lastTodoId = 0; + displayTiddler_patchedByTodo(src, title, state, highlightText, highlightCaseSensitive, animate, slowly); +} + +config.macros.todo = {}; +config.macros.todo.handler = function(place, macroName, params) { + var tiddler = findContainingTiddler(place); + if (!tiddler) return; + + var title = tiddler.id.substr(7); + var todoId = lastTodoId++; + var cbId = "todo" + todoId + "@" + title; + var wrapperId = "todoWrapper" + todoId + "@:" + title; + + var options = new Options(params); + var done = options.done? true: false; + var dispdate = Bool.valueOf(options.get("dispdate")); + var text = (options.args[0]? options.args[0]: "todo #" + todoId) + (done && dispdate? " (fait le " + options.done + ")": ""); + var onClick = function(e) { + if (!e) var e = window.event; + + if (!readOnly) { + // lire les infos sur le tiddler + var body = store.getTiddlerText(title); + var tags = Tiddlers.getTags(title); + + // trouver la macro <]*)>>", "mg"); + do { + var mo = re_macro.exec(body); + if (mo) { + if (id++ == todoId) break; + } + } while (mo); + + // changer la valeur + if (mo) { + var now = new Date(); + + options = new Options(mo[1].readMacroParams()); + var done = options.done? false: true; // logique inversée! + if (done) { + options.done = now.formatFr(); + } else { + delete options.done; + } + body = body.substr(0, mo.index) + "<>" + body.substr(mo.index + mo[0].length); + + lastTodoId = 0; + store.saveTiddler(title, title, body, config.options.txtUserName, now, tags); + if (config.options.chkAutoSave) saveChanges(); + } + } + + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + return false; + }; + + var span = createTiddlyElement(place, "span"); + var cb = createTiddlyElement(span, "input"); + cb.type = "checkbox"; + cb.id = cbId; + cb.checked = done; + cb.onclick = onClick; + var label = createTiddlyElement(span, "label"); + label.setAttribute("for", cbId); + var wrapper = createTiddlyElement(label, "span", wrapperId); + if (done) { + wrapper.setAttribute("style", "text-decoration: line-through;"); + } + createTiddlyText(wrapper, text); +} diff --git a/legacy/twinc/patches.js b/legacy/twinc/patches.js new file mode 100644 index 0000000..ba68472 --- /dev/null +++ b/legacy/twinc/patches.js @@ -0,0 +1,47 @@ +// -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +// correction de quelques méthodes qui ont des bugs + +TiddlyWiki.prototype.tiddlerExists = function(title) +{ + // reconnaire les shadowTiddlers qui ont des string literal + var t = this.tiddlers[title]; + var s = config.shadowTiddlers[title]; + return (t != undefined && t instanceof Tiddler) || (s != undefined && (s instanceof String || typeof(s) == "string")); +} + +TiddlyWiki.prototype.getTiddlerSubtitle = function(title) +{ + // une fonction pour retourner la valeur de subtitle pour un tiddler qui est peut-être dans shadowTiddlers + if (!title) return config.messages.subtitleUnknown; + var tiddler = this.tiddlers[title]; + if(tiddler) return tiddler.getSubtitle(); + else return config.messages.subtitleUnknown; +} + +function createTiddlerTitle(title,highlightText,highlightCaseSensitive) { + // fonctions qui utilisent getTiddlerSubtitle() + var theTitle = document.getElementById("title" + title); + if(theTitle) { + removeChildren(theTitle); + createTiddlyText(theTitle,title); + if (store.tiddlerExists(title)) { + theTitle.title = store.getTiddlerSubtitle(title); + } + } +} + +function createTiddlyLink(place,title,includeText) { + // fonctions qui utilisent getTiddlerSubtitle() + var text = includeText ? title : null; + var subTitle, theClass; + if(store.tiddlerExists(title)) { + subTitle = store.getTiddlerSubtitle(title); + theClass = "tiddlyLinkExisting tiddlyLink"; + } else { + subTitle = config.messages.undefinedTiddlerToolTip.format([title]); + theClass = "tiddlyLinkNonExisting tiddlyLink"; + } + var btn = createTiddlyButton(place,text,subTitle,onClickTiddlerLink,theClass); + btn.setAttribute("tiddlyLink",title); + return(btn); +} diff --git a/legacy/twinc/tiddlywiki b/legacy/twinc/tiddlywiki new file mode 100644 index 0000000..469e6e6 --- /dev/null +++ b/legacy/twinc/tiddlywiki @@ -0,0 +1,93 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function get_version() { + # lire le numéro de version dans le fichier $1 + awk '/var[ \t]*version/ { +match($0, /major: [0-9]*,/) +major = substr($0, RSTART + 7, RLENGTH - 8) +match($0, /minor: [0-9]*,/) +minor = substr($0, RSTART + 7, RLENGTH - 8) +match($0, /revision: [0-9]*,/) +revision = substr($0, RSTART + 10, RLENGTH - 11) +print major "." minor "." revision +exit +}' "$1" +} + +function dump_header() { + # lire et afficher le contenu avant-storeArea du tiddlywiki $1 + awk ' +BEGIN { found = 0 } +/
.*<\/div>/ { foundFooter = 1; next } +foundStoreArea && /^[ \t]*<\/div>/ { foundFooter = 1; next } +foundFooter { print } +' "$1" +} + +function dump_storeArea() { + # lire et afficher le storeArea dans le tiddlywiki $1 + awk ' +BEGIN { found = 0 } +/
.*<\/div>/ { found = 0; next } +found && /^[ \t]*<\/div>/ { found = 0; next } +' "$1" +} + +function dump_defaultStoreArea() { + # Générer un storeArea par défaut pour le projet dont le répertoire est $1 + local today="$(date +%Y%m%d%H%M)" + local projdir="${1:-.}" + local title subtitle + if [ -f "$projdir/build.properties" ]; then + local project_name project_desc + file_get_properties "$projdir/build.properties" project_name "" project_desc "" + title="$project_name" + subtitle="$project_desc" + fi + [ -z "$title" ] && title="$(basename "$(abspath "$projdir")")" + [ -z "$subtitle" ] && subtitle="Gestion des tâches" + + echo '
+
'"$title"'
+
'"$subtitle"'
+
' +} + +function replace_storeArea() { + # dans le tiddlywiki $1, remplacer le storeArea par le fichier $2 (par défaut, lu sur stdin) + ( + dump_header "$1" + if [ -z "$2" -o "$2" == "-" ]; then + cat + else + cat "$2" + fi + dump_footer "$1" + ) >"$1.$$" + /bin/mv "$1.$$" "$1" +} + +function upgrade_TiddlyWiki() { + # mettre à jour le tiddly wiki $1 avec $2 + ( + dump_header "$2" + dump_storeArea "$1" + dump_footer "$2" + ) >"$1.$$" + /bin/mv "$1.$$" "$1" +} diff --git a/legacy/vbsinc/base b/legacy/vbsinc/base new file mode 100644 index 0000000..da08f34 --- /dev/null +++ b/legacy/vbsinc/base @@ -0,0 +1,21 @@ +' -*- coding: utf-8 -*- + +Set sh = CreateObject("WScript.Shell") +Set fso = CreateObject("Scripting.FileSystemObject") +windir = fso.getspecialfolder(0).path + +programs = sh.specialfolders("Programs") +allprograms = sh.specialfolders("AllUsersPrograms") +If allprograms = "" Then + allprograms = programs +End If + +desktop = sh.specialfolders("Desktop") +alldesktop = sh.specialfolders("AllUsersDesktop") +If alldesktop = "" Then + alldesktop = desktop +End If + +' Répertoire de base. On assume que c'est le répertoire qui contient le script +basedir = wscript.scriptfullname +basedir = Mid(basedir, 1, Len(basedir) - Len(wscript.scriptname) - 1) diff --git a/legacy/vbsinc/java b/legacy/vbsinc/java new file mode 100644 index 0000000..27ea754 --- /dev/null +++ b/legacy/vbsinc/java @@ -0,0 +1,16 @@ +' -*- coding: utf-8 -*- + +regpath = "HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment" +On Error Resume Next +currentjavaversion = sh.regread(regpath & "\CurrentVersion") +If Err Then + MsgBox "Java n'est pas installé. Installez d'abord Java et relancez ce script", vbCritical + wscript.quit(1) +End If +javadir = sh.regread(regpath & "\" & currentjavaversion & "\JavaHome") +javadir = inputbox("Confirmez le chemin vers l'installation de Java (JRE)", "Répertoire de java", javadir) + +If javadir = "" Then + MsgBox "Installation annulée", vbExclamation + wscript.quit(0) +End If diff --git a/lib/b36sha1.php b/lib/b36sha1.php new file mode 100755 index 0000000..d532ebf --- /dev/null +++ b/lib/b36sha1.php @@ -0,0 +1,94 @@ +#!/usr/bin/env php + 36 || + $destBase < 2 || + $destBase > 36 || + $pad < 1 || + $sourceBase != intval( $sourceBase ) || + $destBase != intval( $destBase ) || + $pad != intval( $pad ) || + !is_string( $input ) || + $input == '' ) { + return false; + } + $digitChars = ( $lowercase ) ? '0123456789abcdefghijklmnopqrstuvwxyz' : '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $inDigits = array(); + $outChars = ''; + + // Decode and validate input string + $input = strtolower( $input ); + for( $i = 0; $i < strlen( $input ); $i++ ) { + $n = strpos( $digitChars, $input[$i] ); + if( $n === false || $n > $sourceBase ) { + return false; + } + $inDigits[] = $n; + } + + // Iterate over the input, modulo-ing out an output digit + // at a time until input is gone. + while( count( $inDigits ) ) { + $work = 0; + $workDigits = array(); + + // Long division... + foreach( $inDigits as $digit ) { + $work *= $sourceBase; + $work += $digit; + + if( $work < $destBase ) { + // Gonna need to pull another digit. + if( count( $workDigits ) ) { + // Avoid zero-padding; this lets us find + // the end of the input very easily when + // length drops to zero. + $workDigits[] = 0; + } + } else { + // Finally! Actual division! + $workDigits[] = intval( $work / $destBase ); + + // Isn't it annoying that most programming languages + // don't have a single divide-and-remainder operator, + // even though the CPU implements it that way? + $work = $work % $destBase; + } + } + + // All that division leaves us with a remainder, + // which is conveniently our next output digit. + $outChars .= $digitChars[$work]; + + // And we continue! + $inDigits = $workDigits; + } + + while( strlen( $outChars ) < $pad ) { + $outChars .= '0'; + } + + return strrev( $outChars ); +} + +function base36Sha1( $text ) { + return wfBaseConvert( sha1( $text ), 16, 36, 31 ); +} + +if ($argc > 1) { + $inf = fopen($argv[1], "rb"); + if ($inf === FALSE) exit(1); + $close = TRUE; +} else { + $inf = STDIN; + $close = FALSE; +} + +$data = stream_get_contents($inf); +fwrite(STDOUT, base36Sha1($data)); +fwrite(STDOUT, "\n"); + +if ($close) fclose($inf); diff --git a/lib/b36sha1.py b/lib/b36sha1.py new file mode 100755 index 0000000..3f72fc1 --- /dev/null +++ b/lib/b36sha1.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +u"""%(scriptname)s: afficher le hash SHA-1 d'un fichier exprimé en base 36 + +USAGE + %(scriptname)s [options] + +OPTIONS + -s, --skip + Ignorer les premières lignes modeline + empty. Le dernier saut de ligne + est toujours ignoré pour compatibilité avec le code PHP de MediaWiki. + -f, --input INPUT + Lire INPUT au lieu de STDIN + --mediawiki MWDIR + Mode de fonctionnement pour le script mediawiki. Une liste de fichiers + est lu sur STDIN, et pour chacun de ces fichiers afficher une ligne de + la forme 'name,path,hash,' où name est le nom du fichier, path son + chemin relativement à MWDIR, et hash le hash SHA-1 exprimé en base 36.""" + +import os, sys, re, csv +from os import path + +try: + from hashlib import sha1 +except ImportError: + from sha import new + sha1 = new + +from ulib.base.base import strip_nl +from ulib.base.args import build_options, get_args + +DIGIT_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz' +def hex2b36(input): + input = input.lower() + inDigits = [DIGIT_CHARS.index(c) for c in input] + outChars = '' + while inDigits: + work = 0 + workDigits = [] + for digit in inDigits: + work *= 16 + work += digit + if work < 36: + if workDigits: + workDigits.append(0) + else: + workDigits.append(int(work / 36)) + work = work % 36 + outChars = DIGIT_CHARS[work] + outChars + inDigits = workDigits + while len(outChars) < 31: + outChars = '0' + outChars + return outChars + +RE_MODELINE = re.compile(r'^#') +RE_BLANKLINE = re.compile(r'^$') +def get_digest(inf, skip=False): + skip_modeline = skip + skip_blankline = False + m = sha1() + prevnl = None + while True: + l = inf.readline() + if l == '': break + + if prevnl is not None: + m.update(prevnl) + l0 = strip_nl(l) + prevnl = l[len(l0):] + l = l0 + + if skip_modeline: + if RE_MODELINE.match(l) is not None: + prevnl = None + continue + skip_modeline = False + skip_blankline = True + if skip_blankline: + if RE_BLANKLINE.match(l) is not None: + prevnl = None + continue + skip_blankline = False + + m.update(l) + + hexdigest = m.hexdigest() + return hex2b36(hexdigest) + +HEADERS = ['title', 'path', 'hash', 'doublon'] +def do_mediawiki(inf, mwdir, skip=False, outf=None): + if not mwdir.endswith('/'): mwdir += '/' + if outf is None: outf = sys.stdout + outcsv = csv.writer(outf, lineterminator='\n') + + outcsv.writerow(HEADERS) + while True: + fp = inf.readline() + if fp == '': break + fp = strip_nl(fp) + if fp.startswith(mwdir): fpath = fp[len(mwdir):] + else: fpath = fp + ftitle = path.splitext(path.basename(fpath))[0] + fpinf = open(fp, 'rb') + try: + fhash = get_digest(fpinf, skip) + finally: + fpinf.close() + outcsv.writerow([ftitle, fpath, fhash, '']) + +def display_help(): + uprint(__doc__ % globals()) + +def run_b36sha1(): + options, longoptions = build_options([ + ('h', 'help', "Afficher l'aide"), + ('s', 'skip', "Ignorer les premières lignes et le dernier saut de ligne"), + ('f:', 'input=', "Spécifier un fichier à lire"), + ('m:', 'mediawiki=', "Support pour le script mediawiki"), + ]) + options, args = get_args(None, options, longoptions) + skip = False + inputfile = None + mwdir = None + for option, value in options: + if option in ('-h', '--help'): + display_help() + sys.exit(0) + elif option in ('-s', '--skip'): + skip = True + elif option in ('-f', '--input'): + inputfile = value + elif option in ('-m', '--mediawiki'): + mwdir = value + + if inputfile is None or inputfile == '-': + inf = sys.stdin + close = False + else: + inf = open(inputfile, 'rb') + close = True + + try: + if mwdir is None: print get_digest(inf, skip) + else: do_mediawiki(inf, mwdir, skip) + finally: + if close: inf.close() + +if __name__ == '__main__': + run_b36sha1() diff --git a/lib/backup.commands.template b/lib/backup.commands.template new file mode 100644 index 0000000..2f07c29 --- /dev/null +++ b/lib/backup.commands.template @@ -0,0 +1,14 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Commandes supplémentaires à lancer pour compléter la sauvegarde. Placer ce +# fichier sous le nom backup.commands dans $HOME/etc + +# Ce fichier est sourcé par le script de backup. Les variables suivantes sont +# disponibles: +# backup_destdir -- répertoire qui contient les sauvegardes +# CURRENT -- répertoire dans lequel est effectuée la sauvegarde en cours +# NEXT -- répertoire qui contient la sauvegarde précédente + +# L'exemple suivant faire une sauvegarde depuis un répertoire distant + +#rsync -aHz --delete "jclain@host.domain:." "$backup_destdir/$name" diff --git a/lib/backup.excludes.template b/lib/backup.excludes.template new file mode 100644 index 0000000..2898301 --- /dev/null +++ b/lib/backup.excludes.template @@ -0,0 +1,9 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Fichiers à exclure de la sauvegarde. Placer ce fichier sous le nom +# backup.excludes dans $HOME/etc +**/.mozilla/*/Cache/ +**/.mozilla/firefox/*/Cache/ +**/.thunderbird/*/Cache/ +**/.thumbnails/ +**/.DS_Store diff --git a/lib/backup.includes.template b/lib/backup.includes.template new file mode 100644 index 0000000..49004da --- /dev/null +++ b/lib/backup.includes.template @@ -0,0 +1,14 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Fichiers à inclure dans la sauvegarde. Placer ce fichier sous le nom +# backup.includes dans $HOME/etc +/home/ +/home/jclain/ +/home/jclain/** +/root/ +/root/** +/etc/ +/etc/** + +# Exclure les fichiers qui ne sont pas explicitement inclus dans la liste ci-dessus +- * diff --git a/lib/backup.mount b/lib/backup.mount new file mode 100755 index 0000000..8a74ff2 --- /dev/null +++ b/lib/backup.mount @@ -0,0 +1,70 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +BCKDIR="${1:-/backup}" +VGNAME="$2" +LVNAME="$3" + +############################################################################### +# Ne pas modifier à partir d'ici + +function die() { + [ -n "$*" ] && echo "$*" + exit 1 +} + +mountvg= +mount= +mountrw= + +# Vérifier que le volume group est monté +if [ -n "$VGNAME" ]; then + # Vérifier la présence du volume group + found= + try=1 + while [ "$try" -le 2 ]; do + VGNAMES=($(vgs --noheadings -o vg_name | sed 's/ //g')) + for vgname in "${VGNAMES[@]}"; do + if [ "$vgname" == "$VGNAME" ]; then + found=1 + break + fi + done + if [ -n "$found" ]; then + break + else + vgscan >&/dev/null + try=$(($try + 1)) + fi + done + [ -n "$found" ] || die "ERR volume group $VGNAME not found" + + # Vérifier qu'il est monté + if [ ! -d "/dev/$VGNAME" ]; then + vgchange -ay "$VGNAME" >&/dev/null + mountvg=1 + fi + [ -d "/dev/$VGNAME" ] || die "ERR directory /dev/$VGNAME not found" +fi + +# Vérifier que le répertoire est monté +if [ ! -d "$BCKDIR" ]; then + mkdir "$BCKDIR" >&/dev/null || die "ERR cannot mkdir $BCKDIR" +fi +if ! mount | grep -q "on $BCKDIR "; then + if grep -q "$BCKDIR" /etc/fstab; then + mount "$BCKDIR" >&/dev/null || die "ERR cannot mount $BCKDIR" + mount=1 + elif [ -n "$LVNAME" -a -n "$VGNAME" ]; then + mount "/dev/$VGNAME/$LVNAME" "$BCKDIR" + mount=1 + fi +fi + +# Vérifier que le répertoire est monté en lecture/écriture +if ! mount | grep "on $BCKDIR " | grep -q '(.*rw.*)'; then + mount -o remount,rw "$BCKDIR" >&/dev/null || die "ERR cannot remount $BCKDIR as R/W" + mountrw=1 +fi + +echo OK "$BCKDIR" "$VGNAME" "$LVNAME" ${mountvg:+ mounted_vg}${mount:+ mounted}${mountrw:+ mounted_rw} diff --git a/lib/backup.umount b/lib/backup.umount new file mode 100755 index 0000000..38802d4 --- /dev/null +++ b/lib/backup.umount @@ -0,0 +1,47 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +STATUS="$1" +BCKDIR="${2:-/backup}" +VGNAME="$3" +LVNAME="$4" + +############################################################################### +# Ne pas modifier à partir d'ici + +function die() { + [ -n "$*" ] && echo "$*" + exit 1 +} + +# vérifier le status +[ "$STATUS" == "OK" ] || die "ERR status" +shift + +# sauter les argument (BCKDIR, VGNAME, LVNAME) +shift; shift; shift + +mountro= +umount= +umountvg= + +while [ -n "$1" ]; do + case "$1" in + mounted_rw) mountro=1;; + mounted) umount=1;; + mounted_vg) umountvg=1;; + esac + shift +done + +if [ -n "$mountro" ]; then + mount -o remount,ro "$BCKDIR" >&/dev/null || die "ERR cannot remount $BCKDIR as R/O" +fi +if [ -n "$umount" ]; then + umount "$BCKDIR" >&/dev/null || die "ERR cannot umount $BCKDIR" +fi +if [ -n "$umountvg" ]; then + vgchange -an "$VGNAME" >&/dev/null || die "ERR cannot deactivate $VGNAME" +fi + +echo OK diff --git a/lib/bashrc.d/color_ls.[Darwin] b/lib/bashrc.d/color_ls.[Darwin] new file mode 100644 index 0000000..9bfe8e5 --- /dev/null +++ b/lib/bashrc.d/color_ls.[Darwin] @@ -0,0 +1,5 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +if [ -n "$UTOOLS_LS_ALIASES" ]; then + alias ls="ls -G -F" +fi diff --git a/lib/bashrc.d/color_ls.[Linux] b/lib/bashrc.d/color_ls.[Linux] new file mode 100644 index 0000000..e947868 --- /dev/null +++ b/lib/bashrc.d/color_ls.[Linux] @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +if [ -n "$UTOOLS_LS_ALIASES" ]; then + if [ -r /etc/DIR_COLORS ]; then + eval `dircolors -b /etc/DIR_COLORS` + else + eval `dircolors -b` + fi + alias ls="ls --color=auto -F" +fi diff --git a/lib/bashrc.d/color_ls.[SunOS] b/lib/bashrc.d/color_ls.[SunOS] new file mode 100644 index 0000000..a92eb2a --- /dev/null +++ b/lib/bashrc.d/color_ls.[SunOS] @@ -0,0 +1,5 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +if [ -n "$UTOOLS_LS_ALIASES" ]; then + alias ls="ls -F" +fi diff --git a/lib/bashrc.d/confirm_rm,mv,cp b/lib/bashrc.d/confirm_rm,mv,cp new file mode 100644 index 0000000..46b675c --- /dev/null +++ b/lib/bashrc.d/confirm_rm,mv,cp @@ -0,0 +1,7 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +if [ -n "$UTOOLS_CONFIRMATIONS" ]; then + alias rm="rm -i" + alias mv="mv -i" + alias cp="cp -i" +fi diff --git a/lib/bashrc.d/ls_options b/lib/bashrc.d/ls_options new file mode 100644 index 0000000..c7b7b7e --- /dev/null +++ b/lib/bashrc.d/ls_options @@ -0,0 +1,8 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +if [ -n "$UTOOLS_LS_ALIASES" ]; then + alias la="ls -a" + alias ll="ls -l" + alias lla="ls -la" + alias lsd="ls -ld" +fi diff --git a/lib/bashrc.d/nutools.userconf b/lib/bashrc.d/nutools.userconf new file mode 100644 index 0000000..d505c04 --- /dev/null +++ b/lib/bashrc.d/nutools.userconf @@ -0,0 +1,13 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@before * + +# Activer les alias de ls pour afficher en couleur +#export UTOOLS_LS_ALIASES=1 + +# Activer les confirmations pour cp, mv, rm +#export UTOOLS_CONFIRMATIONS=1 + +# utools utilise su plutôt que sudo pour la fonction run_as_root et le script +# _root. Par défaut, ce n'est le cas que si sudo n'est pas installé. Si sudo est +# configuré, il est préférable de ne pas utiliser su. +#export UTOOLS_USES_SU=false diff --git a/lib/default/authftp b/lib/default/authftp new file mode 100644 index 0000000..e04c44b --- /dev/null +++ b/lib/default/authftp @@ -0,0 +1,7 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Faut-il désactiver l'utilisation du proxy FTP? +#AUTHFTP_PROXY_DISABLED=1 + +# Proxy FTP à utiliser pour accéder aux sites FTP authentifiés +AUTHFTP_PROXY_HOST=ftp-proxy.univ.run diff --git a/lib/default/dokuwiki b/lib/default/dokuwiki new file mode 100644 index 0000000..34c7470 --- /dev/null +++ b/lib/default/dokuwiki @@ -0,0 +1,19 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Répertoire du dokuwiki à utiliser par défaut si le répertoire courant n'est +# pas dans un dokuwiki +#DOKUWIKIDIR= + +# Enregistrer automatiquement les modifications dans le gestionnaire de version +# (commit) après la création d'une page ou son édition. Cette option est activée +# par défaut. +#DWCOMMIT=1 + +# Destination par défaut de la synchronisation avec dwsync. +#DWSYNC_DESTDIR=/var/www/dokuwiki + +# Modes et propriétaire pour les répertoires et les fichiers lors de la +# synchronisation avec dwsync. +#DWSYNC_DIRMODE=775 +#DWSYNC_FILEMODE=664 +#DWSYNC_OWNER=www-data: diff --git a/lib/default/mediawiki b/lib/default/mediawiki new file mode 100644 index 0000000..aa4cbca --- /dev/null +++ b/lib/default/mediawiki @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Répertoire du mediawiki à utiliser par défaut si le répertoire courant n'est +# pas dans un mediawiki +#MEDIAWIKIDIR= + +# Enregistrer automatiquement les modifications dans le gestionnaire de version +# (commit) après la création d'une page ou son édition. Cette option est activée +# par défaut. +#MWCOMMIT=1 diff --git a/lib/default/proxy b/lib/default/proxy new file mode 100644 index 0000000..2afe681 --- /dev/null +++ b/lib/default/proxy @@ -0,0 +1,18 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Ce fichier contient la configuration du proxy à utiliser pour accéder à +# internet, dans le cas où le fichier /etc/uproxy.conf n'existe pas. + +# Credentials si le proxy est authentifié +PROXY_LOGIN= +PROXY_PASSWORD= + +# domaines locaux, pour lesquels on ne doit pas passer par le proxy +PROXY_LOCAL_DOMAINS=() + +# proxy http +HTTP_PROXY_HOST= +HTTP_PROXY_PORT= + +# proxy ftp anonyme +FTP_PROXY_HOST="$HTTP_PROXY_HOST" +FTP_PROXY_PORT="$HTTP_PROXY_PORT" diff --git a/lib/default/pubkeys b/lib/default/pubkeys new file mode 100644 index 0000000..25faee0 --- /dev/null +++ b/lib/default/pubkeys @@ -0,0 +1,5 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Clés publiques à installer sur les serveurs distants pour le déploiement à +# distance avec ruinst. Chaque clé doit être sur une ligne séparée. +PUBKEYS= diff --git a/lib/default/runs b/lib/default/runs new file mode 100644 index 0000000..d92fb23 --- /dev/null +++ b/lib/default/runs @@ -0,0 +1,18 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Liste des répertoires à considérer pour la recherche de scripts runs +#RUNSSCRIPTSPATH= + +# Liste des répertoires à considérer pour la recherche de modules installables +#RUNSMODULESPATH= + +# Liste des répertoires à considérer pour la recherche de répertoires d'hôtes +#RUNSHOSTSPATH= + +# Liste des domaines à rechercher, si un hôte déjà configuré est donné sans +# domaine. Si cette liste est vide, le fichier /etc/resolv.conf est consulté +#RUNSDOMAINS=() + +# Si un hôte n'est pas trouvé avec RUNSDOMAINS, nom du domaine par défaut à +# utiliser pour certains scripts +#RUNSDOMAIN= diff --git a/lib/default/ubackup b/lib/default/ubackup new file mode 100644 index 0000000..1afd062 --- /dev/null +++ b/lib/default/ubackup @@ -0,0 +1,38 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Profils de sauvegarde +BACKUPS=(DEFAULT) + +# Chaque profil a des paramètres associés. Soit un profil PROFIL, chaque +# paramètre est dans une variable de la forme PROFIL_VAR. Les variables +# suivantes doivent être définies: +# PROFIL_DESTDIR - répertoire à monter pour la sauvegarde +# PROFIL_VGNAME - nom du volume groupe à monter avant la sauvegarde +# PROFIL_LVNAME - nom du volume logique à monter avant la sauvegarde +# PROFIL_INCLUDES - liste des fichiers à inclure +# PROFIL_EXCLUDES - liste des fichiers à exclure +# PROFIL_SCRIPT - script à exécuter après la sauvegarde si elle réussit +# PROFIL_MOUNT - script à utiliser pour monter $PROFIL_{VGNAME,LVNAME,DESTDIR} +# PROFIL_UMOUNT - script à utiliser pour démonter les systèmes de fichier + +# Si PROFIL==DEFAULT, les variables n'ont pas de préfixe: +#DESTDIR=/backup +#VGNAME= +#LVNAME= +#CONFIGDIR="$HOME/etc" +#INCLUDES="backup.includes" +#EXCLUDES="backup.excludes" +#SCRIPT="backup.commands" +#MOUNT= +#UMOUNT= + +# Sinon, les variables ont un préfixe: +#PROFIL_DESTDIR=/backup +#PROFIL_VGNAME= +#PROFIL_LVNAME= +#PROFIL_CONFIGDIR="$HOME/etc" +#PROFIL_INCLUDES="backup.includes" +#PROFIL_EXCLUDES="backup.excludes" +#PROFIL_SCRIPT="backup.commands" +#PROFIL_MOUNT= +#PROFIL_UMOUNT= diff --git a/lib/default/uldap b/lib/default/uldap new file mode 100644 index 0000000..d7a349d --- /dev/null +++ b/lib/default/uldap @@ -0,0 +1,38 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Profils définis. Chaque élément peut être l'argument de la commande profile. +PROFILES=(default) + +# Aliases de noms de profils. Chaque élément est de la forme alias:canonical +# Ce tableau n'est consulté que si le nom de profil n'est pas trouvé dans PROFILES +PROFILE_ALIASES=() + +# Profils d'authentification définis. Chaque élément peut être l'argument de la +# commande auth. +AUTH_PROFILES=(anonymous) + +# Aliases des profils d'authentification. Chaque élément est de la forme +# alias:canonical. Ce tableau n'est consulté que si le nom de profil n'est pas +# trouvé dans AUTH_PROFILES +AUTH_ALIASES=(anon:anonymous) + +# Pour chacun des profils d'authentification définis, il faut définir un tableau +# $name_AUTH avec les variables suivantes: +# binddn: dn de connexion, ou anonymous +# password: mot de passe si binddn!=anonymous +anonymous_AUTH=(binddn=anonymous) + +# Aliases des searchbase. Ces aliases peuvent être utilisés avec la commande cd, +# et les searchbase associés sont exprimés par rapport à $suffix +SEARCHBASE_ALIASES=(p:ou=People g:ou=Groups) + +# Pour chacun des profils définis, il faut définir un tableau $name_PROFILE avec +# les variables suivantes: +# ldapuri: liste d'uris de serveurs ldap, e.g. ldap://server:port/ pour un seul +# serveur, ou "ldap://server1 ldap://server2" pour plusieurs serveurs +# suffix: dn de base. *tous* les dn sont exprimés relativement à ce +# dn. cette valeur est auto-détectée si elle n'est pas fournie +# binddn: dn de connexion, ou anonymous +# password: mot de passe si binddn!=anonymous +# searchbase: dn de base pour la recherche. Par défaut, vaut $suffix +default_PROFILE=() diff --git a/lib/init.d/install-kvm-stop-all b/lib/init.d/install-kvm-stop-all new file mode 100755 index 0000000..ddd3227 --- /dev/null +++ b/lib/init.d/install-kvm-stop-all @@ -0,0 +1,10 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +. "$(dirname "$0")/../../ulib/ulib" && urequire DEFAULTS || exit 1 + +check_sysinfos -s linux -d debian || exit 0 +run_as_root "$@" + +cp "$scriptdir/kvm-stop-all" /etc/init.d +update-rc.d kvm-stop-all defaults 80 20 diff --git a/lib/init.d/install-openvz-fix-etchosts b/lib/init.d/install-openvz-fix-etchosts new file mode 100755 index 0000000..9787000 --- /dev/null +++ b/lib/init.d/install-openvz-fix-etchosts @@ -0,0 +1,10 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +. "$(dirname "$0")/../../ulib/ulib" && urequire DEFAULTS || exit 1 + +check_sysinfos -s linux -d debian || exit 0 +run_as_root "$@" + +cp "$scriptdir/openvz-fix-etchosts" /etc/init.d +update-rc.d openvz-fix-etchosts defaults 80 20 diff --git a/lib/init.d/kvm-stop-all b/lib/init.d/kvm-stop-all new file mode 100755 index 0000000..0253574 --- /dev/null +++ b/lib/init.d/kvm-stop-all @@ -0,0 +1,38 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +### BEGIN INIT INFO +# Provides: kvm-stop-all +# Required-Start: +# Required-Stop: +# Should-Start: +# Should-Stop: libvirt kvm $remote_fs $network $syslog $named +# Default-Start: +# Default-Stop: 0 1 6 +# X-Interactive: true +# Short-Description: stops kvm virtual machines +# Description: stops kvm virtual machines +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@@dest@@ +VIRSH_DEFAULT_CONNECT_URI=qemu:///system +LANG=fr_FR.UTF-8 +UTOOLS_NO_COLORS=1 +export PATH VIRSH_DEFAULT_CONNECT_URI LANG UTOOLS_NO_COLORS + +. /lib/lsb/init-functions + +case "$1" in +start) + ;; +stop) + log_action_msg "Stopping kvm virtual machines..." + SKvm --stop-all + log_end_msg 0 + ;; +*) + log_action_msg "Usage: /etc/init.d/$NAME {start|stop}" + exit 1 + ;; +esac + +exit 0 diff --git a/lib/init.d/openvz-fix-etchosts b/lib/init.d/openvz-fix-etchosts new file mode 100755 index 0000000..29b46b8 --- /dev/null +++ b/lib/init.d/openvz-fix-etchosts @@ -0,0 +1,63 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +### BEGIN INIT INFO +# Provides: openvz-fix-etchosts +# Required-Start: $network +# Required-Stop: +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# X-Interactive: true +# Short-Description: fix /etc/hosts in openvz containers +# Description: fix /etc/hosts for services (e.g. slapd) which need that +# the local hostname does *not* resolve to localhost. In +# practice, remove hostname from the line +# 127.0.0.1 hostname localhost localhost.localdomain +# in /etc/hosts +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +LANG=fr_FR.UTF-8 +export PATH LANG + +. /lib/lsb/init-functions + +function is_openvz_ct() { + local ctid="$(grep envID /proc/self/status | awk '{print $2}')" + [ -n "$ctid" -a "$ctid" != "0" ] +} + +function fix_etchosts() { + local host hostname ip + + # supprimer hostname --> 127.0.0.1 + hostname="$( ip + host="$(hostname -f 2>/dev/null)" + hostname="$(hostname -s 2>/dev/null)" + local ip="$(LANG=C host "$host" 2>/dev/null | awk '/address / { gsub(/^.*address /, ""); print }' | head -n1)" + if [ -n "$ip" ] && ! grep -Fq "$ip" /etc/hosts; then + echo "$ip $host $hostname" >>/etc/hosts + fi +} + +case "$1" in +start) + if is_openvz_ct; then + log_action_msg "Fixing /etc/hosts..." + fix_etchosts + log_end_msg 0 + fi + ;; +stop) + ;; +*) + log_action_msg "Usage: /etc/init.d/$NAME start" + exit 1 + ;; +esac + +exit 0 diff --git a/lib/makeself-2.1.5/COPYING b/lib/makeself-2.1.5/COPYING new file mode 100644 index 0000000..a52b16e --- /dev/null +++ b/lib/makeself-2.1.5/COPYING @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/lib/makeself-2.1.5/README b/lib/makeself-2.1.5/README new file mode 100644 index 0000000..21aa5a8 --- /dev/null +++ b/lib/makeself-2.1.5/README @@ -0,0 +1,307 @@ +The following was generated from http://www.megastep.org/makeself/ +----------------------- + + + #[1]Mobile/PDA + + makeself - Make self-extractable archives on Unix + + [2]makeself.sh is a small shell script that generates a self-extractable + tar.gz archive from a directory. The resulting file appears as a shell + script (many of those have a .run suffix), and can be launched as is. The + archive will then uncompress itself to a temporary directory and an optional + arbitrary command will be executed (for example an installation script). + This is pretty similar to archives generated with WinZip Self-Extractor in + the Windows world. Makeself archives also include checksums for integrity + self-validation (CRC and/or MD5 checksums). + + The makeself.sh script itself is used only to create the archives from a + directory of files. The resultant archive is actually a compressed (using + gzip, bzip2, or compress) TAR archive, with a small shell script stub at the + beginning. This small stub performs all the steps of extracting the files, + running the embedded command, and removing the temporary files when it's all + over. All what the user has to do to install the software contained in such + an archive is to "run" the archive, i.e sh nice-software.run. I recommend + using the "run" (which was introduced by some Makeself archives released by + Loki Software) or "sh" suffix for such archives not to confuse the users, + since they know it's actually shell scripts (with quite a lot of binary data + attached to it though!). + + I am trying to keep the code of this script as portable as possible, i.e + it's not relying on any bash-specific features and only calls commands that + are installed on any functioning UNIX-compatible system. This script as well + as the archives it generates should run on any Unix flavor, with any + compatible Bourne shell, provided of course that the compression programs + are available. + + As of version 2.1, Makeself has been rewritten and tested on the following + platforms : + * Linux (all distributions) + * Sun Solaris (8 tested) + * HP-UX (tested on 11.0 and 11i on HPPA RISC) + * SCO OpenUnix and OpenServer + * IBM AIX 5.1L + * MacOS X (Darwin) + * SGI IRIX 6.5 + * FreeBSD + * UnicOS / Cray + + If you successfully run Makeself and/or archives created with it on another + system, then [3]let me know! + + Examples of publicly available archives made using makeself are : + * Game patches and installers for [4]Id Software games like Quake 3 for + Linux or Return To Castle Wolfenstien ; + * All game patches released by [5]Loki Software for the Linux version of + popular games ; + * The [6]nVidia drivers for Linux + * The installer for the Linux version of [7]Google Earth + * The [8]Makeself distribution itself ;-) + * and countless others... + + Important note for Apache users: By default, most Web servers will think + that Makeself archives are regular text files and thus they may show up as + text in a Web browser. The correct way to prevent this is to add a MIME type + for this file format, like so (in httpd.conf) : + AddType application/x-makeself .run + + Important note for recent GNU/Linux distributions: Archives created with + Makeself prior to v2.1.2 were using an old syntax for the head and tail Unix + commands that is being progressively obsoleted in their GNU forms. Therefore + you may have problems uncompressing some of these archives. A workaround for + this is to set the environment variable $_POSIX2_VERSION to enable the old + syntax, i.e. : + export _POSIX2_VERSION=199209 + +Usage + + The syntax of makeself is the following: + + makeself.sh [args] archive_dir file_name label startup_script [script_args] + * args are optional options for Makeself. The available ones are : + + --version : Prints the version number on stdout, then exits + immediately + + --gzip : Use gzip for compression (is the default on platforms on + which gzip is commonly available, like Linux) + + --bzip2 : Use bzip2 instead of gzip for better compression. The + bzip2 command must be available in the command path. I recommend + that you set the prefix to something like '.bz2.run' for the + archive, so that potential users know that they'll need bzip2 to + extract it. + + --compress : Use the UNIX "compress" command to compress the data. + This should be the default on all platforms that don't have gzip + available. + + --nocomp : Do not use any compression for the archive, which will + then be an uncompressed TAR. + + --notemp : The generated archive will not extract the files to a + temporary directory, but in a new directory created in the current + directory. This is better to distribute software packages that may + extract and compile by themselves (i.e. launch the compilation + through the embedded script). + + --current : Files will be extracted to the current directory, + instead of in a subdirectory. This option implies --notemp above. + + --follow : Follow the symbolic links inside of the archive + directory, i.e. store the files that are being pointed to instead + of the links themselves. + + --append (new in 2.1.x): Append data to an existing archive, + instead of creating a new one. In this mode, the settings from the + original archive are reused (compression type, label, embedded + script), and thus don't need to be specified again on the command + line. + + --header : Makeself 2.0 uses a separate file to store the header + stub, called "makeself-header.sh". By default, it is assumed that + it is stored in the same location as makeself.sh. This option can + be used to specify its actual location if it is stored someplace + else. + + --copy : Upon extraction, the archive will first extract itself to + a temporary directory. The main application of this is to allow + self-contained installers stored in a Makeself archive on a CD, + when the installer program will later need to unmount the CD and + allow a new one to be inserted. This prevents "Filesystem busy" + errors for installers that span multiple CDs. + + --nox11 : Disable the automatic spawning of a new terminal in X11. + + --nowait : When executed from a new X11 terminal, disable the user + prompt at the end of the script execution. + + --nomd5 and --nocrc : Disable the creation of a MD5 / CRC checksum + for the archive. This speeds up the extraction process if integrity + checking is not necessary. + + --lsm file : Provide and LSM file to makeself, that will be + embedded in the generated archive. LSM files are describing a + software package in a way that is easily parseable. The LSM entry + can then be later retrieved using the '-lsm' argument to the + archive. An exemple of a LSM file is provided with Makeself. + * archive_dir is the name of the directory that contains the files to be + archived + * file_name is the name of the archive to be created + * label is an arbitrary text string describing the package. It will be + displayed while extracting the files. + * startup_script is the command to be executed from within the directory + of extracted files. Thus, if you wish to execute a program contain in + this directory, you must prefix your command with "./". For example, + ./program will be fine. The script_args are additionnal arguments for + this command. + + Here is an example, assuming the user has a package image stored in a + /home/joe/mysoft, and he wants to generate a self-extracting package named + mysoft.sh, which will launch the "setup" script initially stored in + /home/joe/mysoft : + + makeself.sh /home/joe/mysoft mysoft.sh "Joe's Nice Software Package" ./setup + Here is also how I created the [9]makeself.run archive which contains the + Makeself distribution : + + makeself.sh --notemp makeself makeself.run "Makeself by Stephane Peter" echo + "Makeself has extracted itself" + + Archives generated with Makeself 2.1 can be passed the following arguments: + + * --keep : Prevent the files to be extracted in a temporary directory that + will be removed after the embedded script's execution. The files will + then be extracted in the current working directory and will stay here + until you remove them. + * --verbose : Will prompt the user before executing the embedded command + * --target dir : Allows to extract the archive in an arbitrary place. + * --nox11 : Do not spawn a X11 terminal. + * --confirm : Prompt the user for confirmation before running the embedded + command. + * --info : Print out general information about the archive (does not + extract). + * --lsm : Print out the LSM entry, if it is present. + * --list : List the files in the archive. + * --check : Check the archive for integrity using the embedded checksums. + Does not extract the archive. + * --nochown : By default, a "chown -R" command is run on the target + directory after extraction, so that all files belong to the current + user. This is mostly needed if you are running as root, as tar will then + try to recreate the initial user ownerships. You may disable this + behavior with this flag. + * --tar : Run the tar command on the contents of the archive, using the + following arguments as parameter for the command. + * --noexec : Do not run the embedded script after extraction. + + Any subsequent arguments to the archive will be passed as additional + arguments to the embedded command. You should explicitly use the -- special + command-line construct before any such options to make sure that Makeself + will not try to interpret them. + +License + + Makeself is covered by the [10]GNU General Public License (GPL) version 2 + and above. Archives generated by Makeself don't have to be placed under this + license (although I encourage it ;-)), since the archive itself is merely + data for Makeself. + +Download + + Get the latest official distribution [11]here (version 2.1.5). + + The latest development version can be grabbed from the Loki Setup CVS + module, at [12]cvs.icculus.org. + +Version history + + * v1.0: Initial public release + * v1.1: The archive can be passed parameters that will be passed on to the + embedded script, thanks to John C. Quillan + * v1.2: Cosmetic updates, support for bzip2 compression and non-temporary + archives. Many ideas thanks to Francois Petitjean. + * v1.3: More patches from Bjarni R. Einarsson and Francois Petitjean: + Support for no compression (--nocomp), script is no longer mandatory, + automatic launch in an xterm, optional verbose output, and -target + archive option to indicate where to extract the files. + * v1.4: Many patches from Francois Petitjean: improved UNIX compatibility, + automatic integrity checking, support of LSM files to get info on the + package at run time.. + * v1.5.x: A lot of bugfixes, and many other patches, including automatic + verification through the usage of checksums. Version 1.5.5 was the + stable release for a long time, even though the Web page didn't get + updated ;-). Makeself was also officially made a part of the [13]Loki + Setup installer, and its source is being maintained as part of this + package. + * v2.0: Complete internal rewrite of Makeself. The command-line parsing + was vastly improved, the overall maintenance of the package was greatly + improved by separating the stub from makeself.sh. Also Makeself was + ported and tested to a variety of Unix platforms. + * v2.0.1: First public release of the new 2.0 branch. Prior versions are + officially obsoleted. This release introduced the '--copy' argument that + was introduced in response to a need for the [14]UT2K3 Linux installer. + * v2.1.0: Big change : Makeself can now support multiple embedded + tarballs, each stored separately with their own checksums. An existing + archive can be updated with the --append flag. Checksums are also better + managed, and the --nochown option for archives appeared. + * v2.1.1: Fixes related to the Unix compression (compress command). Some + Linux distributions made the insane choice to make it unavailable, even + though gzip is capable of uncompressing these files, plus some more + bugfixes in the extraction and checksum code. + * v2.1.2: Some bug fixes. Use head -n to avoid problems with POSIX + conformance. + * v2.1.3: Bug fixes with the command line when spawning terminals. Added + --tar, --noexec for archives. Added --nomd5 and --nocrc to avoid + creating checksums in archives. The embedded script is now run through + "eval". The --info output now includes the command used to create the + archive. A man page was contributed by Bartosz Fenski. + * v2.1.4: Fixed --info output. Generate random directory name when + extracting files to . to avoid problems. Better handling of errors with + wrong permissions for the directory containing the files. Avoid some + race conditions, Unset the $CDPATH variable to avoid problems if it is + set. Better handling of dot files in the archive directory. + * v2.1.5: Made the md5sum detection consistent with the header code. Check + for the presence of the archive directory. Added --encrypt for symmetric + encryption through gpg (Eric Windisch). Added support for the digest + command on Solaris 10 for MD5 checksums. Check for available disk space + before extracting to the target directory (Andreas Schweitzer). Allow + extraction to run asynchronously (patch by Peter Hatch). Use file + descriptors internally to avoid error messages (patch by Kay Tiong + Khoo). + +Links + + * Check out the [15]"Loki setup" installer, used to install many Linux + games and other applications, and of which I am the co-author. Since the + demise of Loki, I am now the official maintainer of the project, and it + is now being hosted on [16]icculus.org, as well as a bunch of other + ex-Loki projects (and a lot of other good stuff!). + * Bjarni R. Einarsson also wrote the setup.sh installer script, inspired + by Makeself. [17]Check it out ! + +Contact + + This script was written by [18]Stéphane Peter (megastep at megastep.org) I + welcome any enhancements and suggestions. + + Contributions were included from John C. Quillan, Bjarni R. Einarsson, + Francois Petitjean, and Ryan C. Gordon, thanks to them! If you think I + forgot your name, don't hesitate to contact me. + + icculus.org also has a [19]Bugzilla server available that allows bug reports + to be submitted for Loki setup, and since Makeself is a part of Loki setup, + you can submit bug reports from there! + _________________________________________________________________ + + + [20]Stéphane Peter + + Last modified: Fri Jan 4 15:51:05 PST 2008 + +References + + 1. http://mowser.com/web/megastep.org/makeself/ + 2. http://www.megastep.org/makeself/makeself.run + 3. mailto:megastep@REMOVEME.megastep.org + 4. http://www.idsoftware.com/ + 5. http://www.lokigames.com/products/myth2/updates.php3 + 6. http://www.nvidia.com/ + 7. http://earth.google.com/ + 8. http://www.megastep.org/makeself/makeself.run + 9. http://www.megastep.org/makeself/makeself.run + 10. http://www.gnu.org/copyleft/gpl.html + 11. http://www.megastep.org/makeself/makeself-2.1.5.run + 12. http://cvs.icculus.org/ + 13. http://www.icculus.org/loki_setup/ + 14. http://www.unrealtournament2003.com/ + 15. http://www.icculus.org/loki_setup/ + 16. http://www.icculus.org/ + 17. http://www.mmedia.is/~bre/programs/setup.sh/ + 18. mailto:megastep@@megastep.org + 19. https://bugzilla.icculus.org/ + 20. mailto:megastep@@megastep.org diff --git a/lib/makeself-2.1.5/TODO b/lib/makeself-2.1.5/TODO new file mode 100644 index 0000000..8bf501b --- /dev/null +++ b/lib/makeself-2.1.5/TODO @@ -0,0 +1,6 @@ +What needs to be done next : + +- Generic compression code (thru a user-defined command) +- Collect names of directories potentially containing md5 program. GUESS_MD5_PATH + +Stphane Peter diff --git a/lib/makeself-2.1.5/makeself-header.sh b/lib/makeself-2.1.5/makeself-header.sh new file mode 100755 index 0000000..376292f --- /dev/null +++ b/lib/makeself-2.1.5/makeself-header.sh @@ -0,0 +1,419 @@ +# -*- coding: utf-8 mode: sh -*- + +cat << EOF > "$archname" +#!/bin/sh +# This script was generated using Makeself $MS_VERSION + +thisarchname=\`basename "\$0"\` +thisarchdir=\`dirname "\$0"\` +thisarchdir=\`cd "\$thisarchdir"; pwd\` +thisarch="\$thisarchdir/\$thisarchname" + +CRCsum="$CRCsum" +MD5="$MD5sum" +TMPROOT=\${TMPDIR:=/tmp} + +label="$LABEL" +script="$SCRIPT" +scriptargs="$SCRIPTARGS" +targetdir="$archdirname" +filesizes="$filesizes" +keep=$KEEP +tmp_archive=$TMP_ARCHIVE + +print_cmd_arg="" +if type printf > /dev/null; then + print_cmd="printf" +elif test -x /usr/ucb/echo; then + print_cmd="/usr/ucb/echo" +else + print_cmd="echo" +fi + +unset CDPATH + +MS_Printf() +{ + \$print_cmd \$print_cmd_arg "\$1" +} + +MS_Progress() +{ + while read a; do + MS_Printf . + done +} + +MS_diskspace() +{ + ( + if test -d /usr/xpg4/bin; then + PATH=/usr/xpg4/bin:\$PATH + fi + df -kP "\$1" | tail -1 | awk '{print \$4}' + ) +} + +MS_dd() +{ + blocks=\`expr \$3 / 1024\` + bytes=\`expr \$3 % 1024\` + dd if="\$1" ibs=\$2 skip=1 obs=1024 conv=sync 2> /dev/null | \\ + { test \$blocks -gt 0 && dd ibs=1024 obs=1024 count=\$blocks ; \\ + test \$bytes -gt 0 && dd ibs=1 obs=1024 count=\$bytes ; } 2> /dev/null +} + +MS_Help() +{ + cat << EOH >&2 +Makeself version $MS_VERSION + 1) Getting help or info about \$0 : + \$0 --help Print this message + \$0 --info Print embedded info : title, default target directory, embedded script ... + \$0 --lsm Print embedded lsm entry (or no LSM) + \$0 --list Print the list of files in the archive + \$0 --check Checks integrity of the archive + + 2) Running \$0 : + \$0 [options] [--] [additional arguments to embedded script] + with following options (in that order) + --tmproot Temporary directory (defaults to /tmp) + --confirm Ask before running embedded script + --noexec Do not run embedded script + --keep Do not erase target directory after running + the embedded script + --nox11 Do not spawn an xterm + --nochown Do not give the extracted files to the current user + --target NewDirectory Extract in NewDirectory + --tar arg1 [arg2 ...] Access the contents of the archive through the tar command + -- Following arguments will be passed to the embedded script +EOH +} + +MS_Check() +{ + OLD_PATH="\$PATH" + PATH=\${GUESS_MD5_PATH:-"\$OLD_PATH:/bin:/usr/bin:/sbin:/usr/local/ssl/bin:/usr/local/bin:/opt/openssl/bin"} + MD5_ARG="" + MD5_PATH=\`exec <&- 2>&-; which md5sum || type md5sum\` + test -x "\$MD5_PATH" || MD5_PATH=\`exec <&- 2>&-; which md5 || type md5\` + test -x "\$MD5_PATH" || MD5_PATH=\`exec <&- 2>&-; which digest || type digest\` + PATH="\$OLD_PATH" + + MS_Printf "Verifying archive integrity..." + offset=\`head -n $SKIP "\$1" | wc -c | tr -d " "\` + verb=\$2 + i=1 + for s in \$filesizes + do + crc=\`echo \$CRCsum | cut -d" " -f\$i\` + if test -x "\$MD5_PATH"; then + if test \`basename \$MD5_PATH\` = digest; then + MD5_ARG="-a md5" + fi + md5=\`echo \$MD5 | cut -d" " -f\$i\` + if test \$md5 = "00000000000000000000000000000000"; then + test x\$verb = xy && echo " \$1 does not contain an embedded MD5 checksum." >&2 + else + md5sum=\`MS_dd "\$1" \$offset \$s | eval "\$MD5_PATH \$MD5_ARG" | cut -b-32\`; + if test "\$md5sum" != "\$md5"; then + echo "Error in MD5 checksums: \$md5sum is different from \$md5" >&2 + exit 2 + else + test x\$verb = xy && MS_Printf " MD5 checksums are OK." >&2 + fi + crc="0000000000"; verb=n + fi + fi + if test \$crc = "0000000000"; then + test x\$verb = xy && echo " \$1 does not contain a CRC checksum." >&2 + else + sum1=\`MS_dd "\$1" \$offset \$s | CMD_ENV=xpg4 cksum | awk '{print \$1}'\` + if test "\$sum1" = "\$crc"; then + test x\$verb = xy && MS_Printf " CRC checksums are OK." >&2 + else + echo "Error in checksums: \$sum1 is different from \$crc" + exit 2; + fi + fi + i=\`expr \$i + 1\` + offset=\`expr \$offset + \$s\` + done + echo " All good." +} + +UnTAR() +{ + tar \$1vf - 2>&1 || { echo Extraction failed. > /dev/tty; kill -15 \$$; } +} + +finish=true +xterm_loop= +nox11=$NOX11 +copy=$COPY +ownership=y +verbose=n + +initargs="\$@" + +while true +do + case "\$1" in + -h | --help) + MS_Help + exit 0 + ;; + --info) + echo Identification: "\$label" + echo Target directory: "\$targetdir" + echo Uncompressed size: $USIZE KB + echo Compression: $COMPRESS + echo Date of packaging: $DATE + echo Built with Makeself version $MS_VERSION on $OSTYPE + echo Build command was: "$MS_COMMAND" + if test x\$script != x; then + echo Script run after extraction: + echo " " \$script \$scriptargs + fi + if test x"$copy" = xcopy; then + echo "Archive will copy itself to a temporary location" + fi + if test x"$KEEP" = xy; then + echo "directory \$targetdir is permanent" + else + echo "\$targetdir will be removed after extraction" + fi + exit 0 + ;; + --dumpconf) + echo LABEL=\"\$label\" + echo SCRIPT=\"\$script\" + echo SCRIPTARGS=\"\$scriptargs\" + echo archdirname=\"$archdirname\" + echo KEEP=$KEEP + echo COMPRESS=$COMPRESS + echo filesizes=\"\$filesizes\" + echo CRCsum=\"\$CRCsum\" + echo MD5sum=\"\$MD5\" + echo OLDUSIZE=$USIZE + echo OLDSKIP=`expr $SKIP + 1` + exit 0 + ;; + --lsm) +cat << EOLSM +EOF +eval "$LSM_CMD" +cat << EOF >> "$archname" +EOLSM + exit 0 + ;; + --list) + echo Target directory: \$targetdir + offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + for s in \$filesizes + do + MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | UnTAR t + offset=\`expr \$offset + \$s\` + done + exit 0 + ;; + --tar) + offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + arg1="\$2" + shift 2 + for s in \$filesizes + do + MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | tar "\$arg1" - \$* + offset=\`expr \$offset + \$s\` + done + exit 0 + ;; + --check) + MS_Check "\$0" y + exit 0 + ;; + --tmproot) + TMPROOT=\`cd "\$2"; pwd\` + shift 2 + ;; + --confirm) + verbose=y + shift + ;; + --noexec) + script="" + shift + ;; + --keep) + keep=y + shift + ;; + --target) + keep=y + targetdir=\${2:-.} + shift 2 + ;; + --nox11) + nox11=y + shift + ;; + --nochown) + ownership=n + shift + ;; + --xwin) + finish="echo Press Return to close this window...; read junk" + xterm_loop=1 + shift + ;; + --phase2) + copy=phase2 + shift + ;; + --) + shift + break ;; + -*) + echo Unrecognized flag : "\$1" >&2 + MS_Help + exit 1 + ;; + *) + break ;; + esac +done + +case "\$copy" in +copy) + tmpdir=\$TMPROOT/makeself.\$RANDOM.\`date +"%y%m%d%H%M%S"\`.\$\$ + mkdir "\$tmpdir" || { + echo "Could not create temporary directory \$tmpdir" >&2 + exit 1 + } + SCRIPT_COPY="\$tmpdir/makeself" + echo "Copying to a temporary location..." >&2 + cp "\$0" "\$SCRIPT_COPY" + chmod +x "\$SCRIPT_COPY" + cd "\$TMPROOT" + exec "\$SCRIPT_COPY" --phase2 -- \$initargs + ;; +phase2) + finish="\$finish ; rm -rf \`dirname \$0\`" + ;; +esac + +if test "\$nox11" = "n"; then + if tty -s; then # Do we have a terminal? + : + else + if test x"\$DISPLAY" != x -a x"\$xterm_loop" = x; then # No, but do we have X? + if xset q > /dev/null 2>&1; then # Check for valid DISPLAY variable + GUESS_XTERMS="xterm rxvt dtterm eterm Eterm kvt konsole aterm" + for a in \$GUESS_XTERMS; do + if type \$a >/dev/null 2>&1; then + XTERM=\$a + break + fi + done + chmod a+x \$0 || echo Please add execution rights on \$0 + if test \`echo "\$0" | cut -c1\` = "/"; then # Spawn a terminal! + exec \$XTERM -title "\$label" -e "\$0" --xwin "\$initargs" + else + exec \$XTERM -title "\$label" -e "./\$0" --xwin "\$initargs" + fi + fi + fi + fi +fi + +if test "\$targetdir" = "."; then + tmpdir="." +else + if test "\$keep" = y; then + echo "Creating directory \$targetdir" >&2 + tmpdir="\$targetdir" + dashp="-p" + else + tmpdir="\$TMPROOT/selfgz\$\$\$RANDOM" + dashp="" + fi + mkdir \$dashp \$tmpdir || { + echo 'Cannot create target directory' \$tmpdir >&2 + echo 'You should try option --target OtherDirectory' >&2 + eval \$finish + exit 1 + } +fi + +location="\`pwd\`" +if test x\$SETUP_NOCHECK != x1; then + MS_Check "\$0" +fi +offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + +if test x"\$verbose" = xy; then + MS_Printf "About to extract $USIZE KB in \$tmpdir ... Proceed ? [Y/n] " + read yn + if test x"\$yn" = xn; then + eval \$finish; exit 1 + fi +fi + +MS_Printf "Uncompressing \$label" +res=3 +if test "\$keep" = n; then + trap 'echo Signal caught, cleaning up >&2; cd \$TMPROOT; /bin/rm -rf \$tmpdir; eval \$finish; exit 15' 1 2 3 15 +fi + +leftspace=\`MS_diskspace \$tmpdir\` +if test \$leftspace -lt $USIZE; then + echo + echo "Not enough space left in "\`dirname \$tmpdir\`" (\$leftspace KB) to decompress \$0 ($USIZE KB)" >&2 + if test "\$keep" = n; then + echo "Consider setting TMPDIR to a directory with more free space." + fi + eval \$finish; exit 1 +fi + +for s in \$filesizes +do + if MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | ( cd "\$tmpdir"; UnTAR x ) | MS_Progress; then + if test x"\$ownership" = xy; then + (PATH=/usr/xpg4/bin:\$PATH; cd "\$tmpdir"; chown -R \`id -u\` .; chgrp -R \`id -g\` .) + fi + else + echo + echo "Unable to decompress \$0" >&2 + eval \$finish; exit 1 + fi + offset=\`expr \$offset + \$s\` +done +echo + +cd "\$tmpdir" +res=0 +if test x"\$script" != x; then + if test x"\$verbose" = xy; then + MS_Printf "OK to execute: \$script \$scriptargs \$* ? [Y/n] " + read yn + if test x"\$yn" = x -o x"\$yn" = xy -o x"\$yn" = xY; then + eval \$script \$scriptargs \$*; res=\$?; + fi + else + eval \$script \$scriptargs \$*; res=\$? + fi + if test \$res -ne 0; then + test x"\$verbose" = xy && echo "The program '\$script' returned an error code (\$res)" >&2 + fi +fi +if test "\$keep" = n; then + cd \$TMPROOT + /bin/rm -rf \$tmpdir +fi +eval \$finish; +if test x"\$tmp_archive" = xy; then + exec /bin/rm -f "\$thisarch" +else + exit \$res +fi +EOF diff --git a/lib/makeself-2.1.5/makeself-header.sh.orig b/lib/makeself-2.1.5/makeself-header.sh.orig new file mode 100755 index 0000000..d1244f7 --- /dev/null +++ b/lib/makeself-2.1.5/makeself-header.sh.orig @@ -0,0 +1,401 @@ +cat << EOF > "$archname" +#!/bin/sh +# This script was generated using Makeself $MS_VERSION + +CRCsum="$CRCsum" +MD5="$MD5sum" +TMPROOT=\${TMPDIR:=/tmp} + +label="$LABEL" +script="$SCRIPT" +scriptargs="$SCRIPTARGS" +targetdir="$archdirname" +filesizes="$filesizes" +keep=$KEEP + +print_cmd_arg="" +if type printf > /dev/null; then + print_cmd="printf" +elif test -x /usr/ucb/echo; then + print_cmd="/usr/ucb/echo" +else + print_cmd="echo" +fi + +unset CDPATH + +MS_Printf() +{ + \$print_cmd \$print_cmd_arg "\$1" +} + +MS_Progress() +{ + while read a; do + MS_Printf . + done +} + +MS_diskspace() +{ + ( + if test -d /usr/xpg4/bin; then + PATH=/usr/xpg4/bin:\$PATH + fi + df -kP "\$1" | tail -1 | awk '{print \$4}' + ) +} + +MS_dd() +{ + blocks=\`expr \$3 / 1024\` + bytes=\`expr \$3 % 1024\` + dd if="\$1" ibs=\$2 skip=1 obs=1024 conv=sync 2> /dev/null | \\ + { test \$blocks -gt 0 && dd ibs=1024 obs=1024 count=\$blocks ; \\ + test \$bytes -gt 0 && dd ibs=1 obs=1024 count=\$bytes ; } 2> /dev/null +} + +MS_Help() +{ + cat << EOH >&2 +Makeself version $MS_VERSION + 1) Getting help or info about \$0 : + \$0 --help Print this message + \$0 --info Print embedded info : title, default target directory, embedded script ... + \$0 --lsm Print embedded lsm entry (or no LSM) + \$0 --list Print the list of files in the archive + \$0 --check Checks integrity of the archive + + 2) Running \$0 : + \$0 [options] [--] [additional arguments to embedded script] + with following options (in that order) + --confirm Ask before running embedded script + --noexec Do not run embedded script + --keep Do not erase target directory after running + the embedded script + --nox11 Do not spawn an xterm + --nochown Do not give the extracted files to the current user + --target NewDirectory Extract in NewDirectory + --tar arg1 [arg2 ...] Access the contents of the archive through the tar command + -- Following arguments will be passed to the embedded script +EOH +} + +MS_Check() +{ + OLD_PATH="\$PATH" + PATH=\${GUESS_MD5_PATH:-"\$OLD_PATH:/bin:/usr/bin:/sbin:/usr/local/ssl/bin:/usr/local/bin:/opt/openssl/bin"} + MD5_ARG="" + MD5_PATH=\`exec <&- 2>&-; which md5sum || type md5sum\` + test -x "\$MD5_PATH" || MD5_PATH=\`exec <&- 2>&-; which md5 || type md5\` + test -x "\$MD5_PATH" || MD5_PATH=\`exec <&- 2>&-; which digest || type digest\` + PATH="\$OLD_PATH" + + MS_Printf "Verifying archive integrity..." + offset=\`head -n $SKIP "\$1" | wc -c | tr -d " "\` + verb=\$2 + i=1 + for s in \$filesizes + do + crc=\`echo \$CRCsum | cut -d" " -f\$i\` + if test -x "\$MD5_PATH"; then + if test \`basename \$MD5_PATH\` = digest; then + MD5_ARG="-a md5" + fi + md5=\`echo \$MD5 | cut -d" " -f\$i\` + if test \$md5 = "00000000000000000000000000000000"; then + test x\$verb = xy && echo " \$1 does not contain an embedded MD5 checksum." >&2 + else + md5sum=\`MS_dd "\$1" \$offset \$s | eval "\$MD5_PATH \$MD5_ARG" | cut -b-32\`; + if test "\$md5sum" != "\$md5"; then + echo "Error in MD5 checksums: \$md5sum is different from \$md5" >&2 + exit 2 + else + test x\$verb = xy && MS_Printf " MD5 checksums are OK." >&2 + fi + crc="0000000000"; verb=n + fi + fi + if test \$crc = "0000000000"; then + test x\$verb = xy && echo " \$1 does not contain a CRC checksum." >&2 + else + sum1=\`MS_dd "\$1" \$offset \$s | CMD_ENV=xpg4 cksum | awk '{print \$1}'\` + if test "\$sum1" = "\$crc"; then + test x\$verb = xy && MS_Printf " CRC checksums are OK." >&2 + else + echo "Error in checksums: \$sum1 is different from \$crc" + exit 2; + fi + fi + i=\`expr \$i + 1\` + offset=\`expr \$offset + \$s\` + done + echo " All good." +} + +UnTAR() +{ + tar \$1vf - 2>&1 || { echo Extraction failed. > /dev/tty; kill -15 \$$; } +} + +finish=true +xterm_loop= +nox11=$NOX11 +copy=$COPY +ownership=y +verbose=n + +initargs="\$@" + +while true +do + case "\$1" in + -h | --help) + MS_Help + exit 0 + ;; + --info) + echo Identification: "\$label" + echo Target directory: "\$targetdir" + echo Uncompressed size: $USIZE KB + echo Compression: $COMPRESS + echo Date of packaging: $DATE + echo Built with Makeself version $MS_VERSION on $OSTYPE + echo Build command was: "$MS_COMMAND" + if test x\$script != x; then + echo Script run after extraction: + echo " " \$script \$scriptargs + fi + if test x"$copy" = xcopy; then + echo "Archive will copy itself to a temporary location" + fi + if test x"$KEEP" = xy; then + echo "directory \$targetdir is permanent" + else + echo "\$targetdir will be removed after extraction" + fi + exit 0 + ;; + --dumpconf) + echo LABEL=\"\$label\" + echo SCRIPT=\"\$script\" + echo SCRIPTARGS=\"\$scriptargs\" + echo archdirname=\"$archdirname\" + echo KEEP=$KEEP + echo COMPRESS=$COMPRESS + echo filesizes=\"\$filesizes\" + echo CRCsum=\"\$CRCsum\" + echo MD5sum=\"\$MD5\" + echo OLDUSIZE=$USIZE + echo OLDSKIP=`expr $SKIP + 1` + exit 0 + ;; + --lsm) +cat << EOLSM +EOF +eval "$LSM_CMD" +cat << EOF >> "$archname" +EOLSM + exit 0 + ;; + --list) + echo Target directory: \$targetdir + offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + for s in \$filesizes + do + MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | UnTAR t + offset=\`expr \$offset + \$s\` + done + exit 0 + ;; + --tar) + offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + arg1="\$2" + shift 2 + for s in \$filesizes + do + MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | tar "\$arg1" - \$* + offset=\`expr \$offset + \$s\` + done + exit 0 + ;; + --check) + MS_Check "\$0" y + exit 0 + ;; + --confirm) + verbose=y + shift + ;; + --noexec) + script="" + shift + ;; + --keep) + keep=y + shift + ;; + --target) + keep=y + targetdir=\${2:-.} + shift 2 + ;; + --nox11) + nox11=y + shift + ;; + --nochown) + ownership=n + shift + ;; + --xwin) + finish="echo Press Return to close this window...; read junk" + xterm_loop=1 + shift + ;; + --phase2) + copy=phase2 + shift + ;; + --) + shift + break ;; + -*) + echo Unrecognized flag : "\$1" >&2 + MS_Help + exit 1 + ;; + *) + break ;; + esac +done + +case "\$copy" in +copy) + tmpdir=\$TMPROOT/makeself.\$RANDOM.\`date +"%y%m%d%H%M%S"\`.\$\$ + mkdir "\$tmpdir" || { + echo "Could not create temporary directory \$tmpdir" >&2 + exit 1 + } + SCRIPT_COPY="\$tmpdir/makeself" + echo "Copying to a temporary location..." >&2 + cp "\$0" "\$SCRIPT_COPY" + chmod +x "\$SCRIPT_COPY" + cd "\$TMPROOT" + exec "\$SCRIPT_COPY" --phase2 -- \$initargs + ;; +phase2) + finish="\$finish ; rm -rf \`dirname \$0\`" + ;; +esac + +if test "\$nox11" = "n"; then + if tty -s; then # Do we have a terminal? + : + else + if test x"\$DISPLAY" != x -a x"\$xterm_loop" = x; then # No, but do we have X? + if xset q > /dev/null 2>&1; then # Check for valid DISPLAY variable + GUESS_XTERMS="xterm rxvt dtterm eterm Eterm kvt konsole aterm" + for a in \$GUESS_XTERMS; do + if type \$a >/dev/null 2>&1; then + XTERM=\$a + break + fi + done + chmod a+x \$0 || echo Please add execution rights on \$0 + if test \`echo "\$0" | cut -c1\` = "/"; then # Spawn a terminal! + exec \$XTERM -title "\$label" -e "\$0" --xwin "\$initargs" + else + exec \$XTERM -title "\$label" -e "./\$0" --xwin "\$initargs" + fi + fi + fi + fi +fi + +if test "\$targetdir" = "."; then + tmpdir="." +else + if test "\$keep" = y; then + echo "Creating directory \$targetdir" >&2 + tmpdir="\$targetdir" + dashp="-p" + else + tmpdir="\$TMPROOT/selfgz\$\$\$RANDOM" + dashp="" + fi + mkdir \$dashp \$tmpdir || { + echo 'Cannot create target directory' \$tmpdir >&2 + echo 'You should try option --target OtherDirectory' >&2 + eval \$finish + exit 1 + } +fi + +location="\`pwd\`" +if test x\$SETUP_NOCHECK != x1; then + MS_Check "\$0" +fi +offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + +if test x"\$verbose" = xy; then + MS_Printf "About to extract $USIZE KB in \$tmpdir ... Proceed ? [Y/n] " + read yn + if test x"\$yn" = xn; then + eval \$finish; exit 1 + fi +fi + +MS_Printf "Uncompressing \$label" +res=3 +if test "\$keep" = n; then + trap 'echo Signal caught, cleaning up >&2; cd \$TMPROOT; /bin/rm -rf \$tmpdir; eval \$finish; exit 15' 1 2 3 15 +fi + +leftspace=\`MS_diskspace \$tmpdir\` +if test \$leftspace -lt $USIZE; then + echo + echo "Not enough space left in "\`dirname \$tmpdir\`" (\$leftspace KB) to decompress \$0 ($USIZE KB)" >&2 + if test "\$keep" = n; then + echo "Consider setting TMPDIR to a directory with more free space." + fi + eval \$finish; exit 1 +fi + +for s in \$filesizes +do + if MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | ( cd "\$tmpdir"; UnTAR x ) | MS_Progress; then + if test x"\$ownership" = xy; then + (PATH=/usr/xpg4/bin:\$PATH; cd "\$tmpdir"; chown -R \`id -u\` .; chgrp -R \`id -g\` .) + fi + else + echo + echo "Unable to decompress \$0" >&2 + eval \$finish; exit 1 + fi + offset=\`expr \$offset + \$s\` +done +echo + +cd "\$tmpdir" +res=0 +if test x"\$script" != x; then + if test x"\$verbose" = xy; then + MS_Printf "OK to execute: \$script \$scriptargs \$* ? [Y/n] " + read yn + if test x"\$yn" = x -o x"\$yn" = xy -o x"\$yn" = xY; then + eval \$script \$scriptargs \$*; res=\$?; + fi + else + eval \$script \$scriptargs \$*; res=\$? + fi + if test \$res -ne 0; then + test x"\$verbose" = xy && echo "The program '\$script' returned an error code (\$res)" >&2 + fi +fi +if test "\$keep" = n; then + cd \$TMPROOT + /bin/rm -rf \$tmpdir +fi +eval \$finish; exit \$res +EOF diff --git a/lib/makeself-2.1.5/makeself.1 b/lib/makeself-2.1.5/makeself.1 new file mode 100644 index 0000000..d0f2caa --- /dev/null +++ b/lib/makeself-2.1.5/makeself.1 @@ -0,0 +1,76 @@ +.TH "makeself" "1" "2.1.4" +.SH "NAME" +makeself \- An utility to generate self-extractable archives. +.SH "SYNTAX" +.LP +.B makeself [\fIoptions\fP] archive_dir file_name label +.B [\fIstartup_script\fP] [\fIargs\fP] +.SH "DESCRIPTION" +.LP +This program is a free (GPL) utility designed to create self-extractable +archives from a directory. +.br +.SH "OPTIONS" +.LP +The following options are supported. +.LP +.TP 15 +.B -v, --version +Prints out the makeself version number and exits. +.TP +.B -h, --help +Print out help information. +.TP +.B --gzip +Compress using gzip (default if detected). +.TP +.B --bzip2 +Compress using bzip2. +.TP +.B --compress +Compress using the UNIX 'compress' command. +.TP +.B --nocomp +Do not compress the data. +.TP +.B --notemp +The archive will create archive_dir in the current directory and +uncompress in ./archive_dir. +.TP +.B --copy +Upon extraction, the archive will first copy itself to a temporary directory. +.TP +.B --append +Append more files to an existing makeself archive. +The label and startup scripts will then be ignored. +.TP +.B --current +Files will be extracted to the current directory. Implies --notemp. +.TP +.B --header file +Specify location of the header script. +.TP +.B --follow +Follow the symlinks in the archive. +.TP +.B --nox11 +Disable automatic spawn of an xterm if running in X11. +.TP +.B --nowait +Do not wait for user input after executing embedded program from an xterm. +.TP +.B --nomd5 +Do not create a MD5 checksum for the archive. +.TP +.B --nocrc +Do not create a CRC32 checksum for the archive. +.TP +.B --lsm file +LSM file describing the package. +.PD +.SH "AUTHORS" +.LP +Makeself has been written by Stphane Peter . +.BR +This man page was originally written by Bartosz Fenski for the +Debian GNU/Linux distribution (but it may be used by others). diff --git a/lib/makeself-2.1.5/makeself.lsm b/lib/makeself-2.1.5/makeself.lsm new file mode 100644 index 0000000..1d28ba5 --- /dev/null +++ b/lib/makeself-2.1.5/makeself.lsm @@ -0,0 +1,16 @@ +Begin3 +Title: makeself.sh +Version: 2.1 +Description: makeself.sh is a shell script that generates a self-extractable + tar.gz archive from a directory. The resulting file appears as a shell + script, and can be launched as is. The archive will then uncompress + itself to a temporary directory and an arbitrary command will be + executed (for example an installation script). This is pretty similar + to archives generated with WinZip Self-Extractor in the Windows world. +Keywords: Installation archive tar winzip +Author: Stphane Peter (megastep@megastep.org) +Maintained-by: Stphane Peter (megastep@megastep.org) +Original-site: http://www.megastep.org/makeself/ +Platform: Unix +Copying-policy: GPL +End diff --git a/lib/makeself-2.1.5/makeself.sh b/lib/makeself-2.1.5/makeself.sh new file mode 100755 index 0000000..fef1957 --- /dev/null +++ b/lib/makeself-2.1.5/makeself.sh @@ -0,0 +1,419 @@ +#!/bin/sh +# -*- coding: utf-8 mode: sh -*- +# +# Makeself version 2.1.x +# by Stephane Peter +# +# $Id: makeself.sh,v 1.64 2008/01/04 23:52:14 megastep Exp $ +# +# Utility to create self-extracting tar.gz archives. +# The resulting archive is a file holding the tar.gz archive with +# a small Shell script stub that uncompresses the archive to a temporary +# directory and then executes a given script from withing that directory. +# +# Makeself home page: http://www.megastep.org/makeself/ +# +# Version 2.0 is a rewrite of version 1.0 to make the code easier to read and maintain. +# +# Version history : +# - 1.0 : Initial public release +# - 1.1 : The archive can be passed parameters that will be passed on to +# the embedded script, thanks to John C. Quillan +# - 1.2 : Package distribution, bzip2 compression, more command line options, +# support for non-temporary archives. Ideas thanks to Francois Petitjean +# - 1.3 : More patches from Bjarni R. Einarsson and Francois Petitjean: +# Support for no compression (--nocomp), script is no longer mandatory, +# automatic launch in an xterm, optional verbose output, and -target +# archive option to indicate where to extract the files. +# - 1.4 : Improved UNIX compatibility (Francois Petitjean) +# Automatic integrity checking, support of LSM files (Francois Petitjean) +# - 1.5 : Many bugfixes. Optionally disable xterm spawning. +# - 1.5.1 : More bugfixes, added archive options -list and -check. +# - 1.5.2 : Cosmetic changes to inform the user of what's going on with big +# archives (Quake III demo) +# - 1.5.3 : Check for validity of the DISPLAY variable before launching an xterm. +# More verbosity in xterms and check for embedded command's return value. +# Bugfix for Debian 2.0 systems that have a different "print" command. +# - 1.5.4 : Many bugfixes. Print out a message if the extraction failed. +# - 1.5.5 : More bugfixes. Added support for SETUP_NOCHECK environment variable to +# bypass checksum verification of archives. +# - 1.6.0 : Compute MD5 checksums with the md5sum command (patch from Ryan Gordon) +# - 2.0 : Brand new rewrite, cleaner architecture, separated header and UNIX ports. +# - 2.0.1 : Added --copy +# - 2.1.0 : Allow multiple tarballs to be stored in one archive, and incremental updates. +# Added --nochown for archives +# Stopped doing redundant checksums when not necesary +# - 2.1.1 : Work around insane behavior from certain Linux distros with no 'uncompress' command +# Cleaned up the code to handle error codes from compress. Simplified the extraction code. +# - 2.1.2 : Some bug fixes. Use head -n to avoid problems. +# - 2.1.3 : Bug fixes with command line when spawning terminals. +# Added --tar for archives, allowing to give arbitrary arguments to tar on the contents of the archive. +# Added --noexec to prevent execution of embedded scripts. +# Added --nomd5 and --nocrc to avoid creating checksums in archives. +# Added command used to create the archive in --info output. +# Run the embedded script through eval. +# - 2.1.4 : Fixed --info output. +# Generate random directory name when extracting files to . to avoid problems. (Jason Trent) +# Better handling of errors with wrong permissions for the directory containing the files. (Jason Trent) +# Avoid some race conditions (Ludwig Nussel) +# Unset the $CDPATH variable to avoid problems if it is set. (Debian) +# Better handling of dot files in the archive directory. +# - 2.1.5 : Made the md5sum detection consistent with the header code. +# Check for the presence of the archive directory +# Added --encrypt for symmetric encryption through gpg (Eric Windisch) +# Added support for the digest command on Solaris 10 for MD5 checksums +# Check for available disk space before extracting to the target directory (Andreas Schweitzer) +# Allow extraction to run asynchronously (patch by Peter Hatch) +# Use file descriptors internally to avoid error messages (patch by Kay Tiong Khoo) +# +# (C) 1998-2008 by Stéphane Peter +# +# This software is released under the terms of the GNU GPL version 2 and above +# Please read the license at http://www.gnu.org/copyleft/gpl.html +# + +MS_VERSION=2.1.5 +MS_COMMAND="$0" +unset CDPATH + +for f in "${1+"$@"}"; do + MS_COMMAND="$MS_COMMAND \\\\ + \\\"$f\\\"" +done + +# Procedures + +MS_Usage() +{ + echo "Usage: $0 [params] archive_dir file_name label [startup_script] [args]" + echo "params can be one or more of the following :" + echo " --version | -v : Print out Makeself version number and exit" + echo " --help | -h : Print out this help message" + echo " --gzip : Compress using gzip (default if detected)" + echo " --bzip2 : Compress using bzip2 instead of gzip" + echo " --compress : Compress using the UNIX 'compress' command" + echo " --nocomp : Do not compress the data" + echo " --notemp : The archive will create archive_dir in the" + echo " current directory and uncompress in ./archive_dir" + echo " --copy : Upon extraction, the archive will first copy itself to" + echo " a temporary directory" + echo " --append : Append more files to an existing Makeself archive" + echo " The label and startup scripts will then be ignored" + echo " --current : Files will be extracted to the current directory." + echo " Implies --notemp." + echo " --nomd5 : Don't calculate an MD5 for archive" + echo " --nocrc : Don't calculate a CRC for archive" + echo " --header file : Specify location of the header script" + echo " --follow : Follow the symlinks in the archive" + echo " --nox11 : Disable automatic spawn of a xterm" + echo " --nowait : Do not wait for user input after executing embedded" + echo " program from an xterm" + echo " --lsm file : LSM file describing the package" + echo + echo "Do not forget to give a fully qualified startup script name" + echo "(i.e. with a ./ prefix if inside the archive)." + exit 1 +} + +# Default settings +ECHO=echo +if type gzip 2>&1 > /dev/null; then + COMPRESS=gzip +else + COMPRESS=Unix +fi +KEEP=n +TMP_ARCHIVE=n +CURRENT=n +NOX11=n +APPEND=n +COPY=none +TAR_ARGS=cvf +HEADER=`dirname $0`/makeself-header.sh + +# LSM file stuff +LSM_CMD="echo No LSM. >> \"\$archname\"" + +while true +do + case "$1" in + --version | -v) + echo Makeself version $MS_VERSION + exit 0 + ;; + --quiet) + ECHO=true + TAR_ARGS=cf + shift + ;; + --bzip2) + COMPRESS=bzip2 + shift + ;; + --gzip) + COMPRESS=gzip + shift + ;; + --compress) + COMPRESS=Unix + shift + ;; + --encrypt) + COMPRESS=gpg + shift + ;; + --nocomp) + COMPRESS=none + shift + ;; + --notemp) + KEEP=y + shift + ;; + --copy) + COPY=copy + shift + ;; + --current) + CURRENT=y + KEEP=y + shift + ;; + --tmp-archive) + TMP_ARCHIVE=y + shift + ;; + --header) + HEADER="$2" + shift 2 + ;; + --follow) + TAR_ARGS=cvfh + shift + ;; + --nox11) + NOX11=y + shift + ;; + --nowait) + shift + ;; + --nomd5) + NOMD5=y + shift + ;; + --nocrc) + NOCRC=y + shift + ;; + --append) + APPEND=y + shift + ;; + --lsm) + LSM_CMD="cat \"$2\" >> \"\$archname\"" + shift 2 + ;; + -h | --help) + MS_Usage + ;; + -*) + echo Unrecognized flag : "$1" + MS_Usage + ;; + *) + break + ;; + esac +done + +if test $# -lt 1; then + MS_Usage +else + if test -d "$1"; then + archdir="$1" + else + $ECHO "Directory $1 does not exist." + exit 1 + fi +fi +archname="$2" + +if test "$APPEND" = y; then + if test $# -lt 2; then + MS_Usage + fi + + # Gather the info from the original archive + OLDENV=`sh "$archname" --dumpconf` + if test $? -ne 0; then + $ECHO "Unable to update archive: $archname" >&2 + exit 1 + else + eval "$OLDENV" + fi +else + if test "$KEEP" = n -a $# = 3; then + $ECHO "ERROR: Making a temporary archive with no embedded command does not make sense!" >&2 + $ECHO + MS_Usage + fi + # We don't really want to create an absolute directory... + if test "$CURRENT" = y; then + archdirname="." + else + archdirname=`basename "$1"` + fi + + if test $# -lt 3; then + MS_Usage + fi + + LABEL="$3" + SCRIPT="$4" + test x$SCRIPT = x || shift 1 + shift 3 + SCRIPTARGS="$*" +fi + +if test "$KEEP" = n -a "$CURRENT" = y; then + $ECHO "ERROR: It is A VERY DANGEROUS IDEA to try to combine --notemp and --current." >&2 + exit 1 +fi + +case $COMPRESS in +gzip) + GZIP_CMD="gzip -c9" + GUNZIP_CMD="gzip -cd" + ;; +bzip2) + GZIP_CMD="bzip2 -9" + GUNZIP_CMD="bzip2 -d" + ;; +gpg) + GZIP_CMD="gpg -ac -z9" + GUNZIP_CMD="gpg -d" + ;; +Unix) + GZIP_CMD="compress -cf" + GUNZIP_CMD="exec 2>&-; uncompress -c || test \\\$? -eq 2 || gzip -cd" + ;; +none) + GZIP_CMD="cat" + GUNZIP_CMD="cat" + ;; +esac + +tmpfile="${TMPDIR:=/tmp}/mkself$$" + +if test -f $HEADER; then + oldarchname="$archname" + archname="$tmpfile" + # Generate a fake header to count its lines + SKIP=0 + . $HEADER + SKIP=`cat "$tmpfile" |wc -l` + # Get rid of any spaces + SKIP=`expr $SKIP` + rm -f "$tmpfile" + $ECHO Header is $SKIP lines long >&2 + + archname="$oldarchname" +else + $ECHO "Unable to open header file: $HEADER" >&2 + exit 1 +fi + +$ECHO + +if test "$APPEND" = n; then + if test -f "$archname"; then + $ECHO "WARNING: Overwriting existing file: $archname" >&2 + fi +fi + +USIZE=`du -ks $archdir | cut -f1` +DATE=`LC_ALL=C date` + +if test "." = "$archdirname"; then + if test "$KEEP" = n; then + archdirname="makeself-$$-`date +%Y%m%d%H%M%S`" + fi +fi + +test -d "$archdir" || { $ECHO "Error: $archdir does not exist."; rm -f "$tmpfile"; exit 1; } +$ECHO About to compress $USIZE KB of data... +$ECHO Adding files to archive named \"$archname\"... +exec 3<> "$tmpfile" +(cd "$archdir" && ( tar $TAR_ARGS - . | eval "$GZIP_CMD" >&3 ) ) || { $ECHO Aborting: Archive directory not found or temporary file: "$tmpfile" could not be created.; exec 3>&-; rm -f "$tmpfile"; exit 1; } +exec 3>&- # try to close the archive + +fsize=`cat "$tmpfile" | wc -c | tr -d " "` + +# Compute the checksums + +md5sum=00000000000000000000000000000000 +crcsum=0000000000 + +if test "$NOCRC" = y; then + $ECHO "skipping crc at user request" +else + crcsum=`cat "$tmpfile" | CMD_ENV=xpg4 cksum | sed -e 's/ /Z/' -e 's/ /Z/' | cut -dZ -f1` + $ECHO "CRC: $crcsum" +fi + +if test "$NOMD5" = y; then + $ECHO "skipping md5sum at user request" +else + # Try to locate a MD5 binary + OLD_PATH=$PATH + PATH=${GUESS_MD5_PATH:-"$OLD_PATH:/bin:/usr/bin:/sbin:/usr/local/ssl/bin:/usr/local/bin:/opt/openssl/bin"} + MD5_ARG="" + MD5_PATH=`exec <&- 2>&-; which md5sum || type md5sum` + test -x $MD5_PATH || MD5_PATH=`exec <&- 2>&-; which md5 || type md5` + test -x $MD5_PATH || MD5_PATH=`exec <&- 2>&-; which digest || type digest` + PATH=$OLD_PATH + if test `basename $MD5_PATH` = digest; then + MD5_ARG="-a md5" + fi + if test -x "$MD5_PATH"; then + md5sum=`cat "$tmpfile" | eval "$MD5_PATH $MD5_ARG" | cut -b-32`; + $ECHO "MD5: $md5sum" + else + $ECHO "MD5: none, MD5 command not found" + fi +fi + +if test "$APPEND" = y; then + mv "$archname" "$archname".bak || exit + + # Prepare entry for new archive + filesizes="$filesizes $fsize" + CRCsum="$CRCsum $crcsum" + MD5sum="$MD5sum $md5sum" + USIZE=`expr $USIZE + $OLDUSIZE` + # Generate the header + . $HEADER + # Append the original data + tail -n +$OLDSKIP "$archname".bak >> "$archname" + # Append the new data + cat "$tmpfile" >> "$archname" + + chmod +x "$archname" + rm -f "$archname".bak + $ECHO Self-extractible archive \"$archname\" successfully updated. +else + filesizes="$fsize" + CRCsum="$crcsum" + MD5sum="$md5sum" + + # Generate the header + . $HEADER + + # Append the compressed tar data after the stub + $ECHO + cat "$tmpfile" >> "$archname" + chmod +x "$archname" + $ECHO Self-extractible archive \"$archname\" successfully created. +fi +rm -f "$tmpfile" diff --git a/lib/makeself-2.1.5/makeself.sh.orig b/lib/makeself-2.1.5/makeself.sh.orig new file mode 100755 index 0000000..3591fc5 --- /dev/null +++ b/lib/makeself-2.1.5/makeself.sh.orig @@ -0,0 +1,407 @@ +#!/bin/sh +# +# Makeself version 2.1.x +# by Stephane Peter +# +# $Id: makeself.sh,v 1.64 2008/01/04 23:52:14 megastep Exp $ +# +# Utility to create self-extracting tar.gz archives. +# The resulting archive is a file holding the tar.gz archive with +# a small Shell script stub that uncompresses the archive to a temporary +# directory and then executes a given script from withing that directory. +# +# Makeself home page: http://www.megastep.org/makeself/ +# +# Version 2.0 is a rewrite of version 1.0 to make the code easier to read and maintain. +# +# Version history : +# - 1.0 : Initial public release +# - 1.1 : The archive can be passed parameters that will be passed on to +# the embedded script, thanks to John C. Quillan +# - 1.2 : Package distribution, bzip2 compression, more command line options, +# support for non-temporary archives. Ideas thanks to Francois Petitjean +# - 1.3 : More patches from Bjarni R. Einarsson and Francois Petitjean: +# Support for no compression (--nocomp), script is no longer mandatory, +# automatic launch in an xterm, optional verbose output, and -target +# archive option to indicate where to extract the files. +# - 1.4 : Improved UNIX compatibility (Francois Petitjean) +# Automatic integrity checking, support of LSM files (Francois Petitjean) +# - 1.5 : Many bugfixes. Optionally disable xterm spawning. +# - 1.5.1 : More bugfixes, added archive options -list and -check. +# - 1.5.2 : Cosmetic changes to inform the user of what's going on with big +# archives (Quake III demo) +# - 1.5.3 : Check for validity of the DISPLAY variable before launching an xterm. +# More verbosity in xterms and check for embedded command's return value. +# Bugfix for Debian 2.0 systems that have a different "print" command. +# - 1.5.4 : Many bugfixes. Print out a message if the extraction failed. +# - 1.5.5 : More bugfixes. Added support for SETUP_NOCHECK environment variable to +# bypass checksum verification of archives. +# - 1.6.0 : Compute MD5 checksums with the md5sum command (patch from Ryan Gordon) +# - 2.0 : Brand new rewrite, cleaner architecture, separated header and UNIX ports. +# - 2.0.1 : Added --copy +# - 2.1.0 : Allow multiple tarballs to be stored in one archive, and incremental updates. +# Added --nochown for archives +# Stopped doing redundant checksums when not necesary +# - 2.1.1 : Work around insane behavior from certain Linux distros with no 'uncompress' command +# Cleaned up the code to handle error codes from compress. Simplified the extraction code. +# - 2.1.2 : Some bug fixes. Use head -n to avoid problems. +# - 2.1.3 : Bug fixes with command line when spawning terminals. +# Added --tar for archives, allowing to give arbitrary arguments to tar on the contents of the archive. +# Added --noexec to prevent execution of embedded scripts. +# Added --nomd5 and --nocrc to avoid creating checksums in archives. +# Added command used to create the archive in --info output. +# Run the embedded script through eval. +# - 2.1.4 : Fixed --info output. +# Generate random directory name when extracting files to . to avoid problems. (Jason Trent) +# Better handling of errors with wrong permissions for the directory containing the files. (Jason Trent) +# Avoid some race conditions (Ludwig Nussel) +# Unset the $CDPATH variable to avoid problems if it is set. (Debian) +# Better handling of dot files in the archive directory. +# - 2.1.5 : Made the md5sum detection consistent with the header code. +# Check for the presence of the archive directory +# Added --encrypt for symmetric encryption through gpg (Eric Windisch) +# Added support for the digest command on Solaris 10 for MD5 checksums +# Check for available disk space before extracting to the target directory (Andreas Schweitzer) +# Allow extraction to run asynchronously (patch by Peter Hatch) +# Use file descriptors internally to avoid error messages (patch by Kay Tiong Khoo) +# +# (C) 1998-2008 by Stéphane Peter +# +# This software is released under the terms of the GNU GPL version 2 and above +# Please read the license at http://www.gnu.org/copyleft/gpl.html +# + +MS_VERSION=2.1.5 +MS_COMMAND="$0" +unset CDPATH + +for f in "${1+"$@"}"; do + MS_COMMAND="$MS_COMMAND \\\\ + \\\"$f\\\"" +done + +# Procedures + +MS_Usage() +{ + echo "Usage: $0 [params] archive_dir file_name label [startup_script] [args]" + echo "params can be one or more of the following :" + echo " --version | -v : Print out Makeself version number and exit" + echo " --help | -h : Print out this help message" + echo " --gzip : Compress using gzip (default if detected)" + echo " --bzip2 : Compress using bzip2 instead of gzip" + echo " --compress : Compress using the UNIX 'compress' command" + echo " --nocomp : Do not compress the data" + echo " --notemp : The archive will create archive_dir in the" + echo " current directory and uncompress in ./archive_dir" + echo " --copy : Upon extraction, the archive will first copy itself to" + echo " a temporary directory" + echo " --append : Append more files to an existing Makeself archive" + echo " The label and startup scripts will then be ignored" + echo " --current : Files will be extracted to the current directory." + echo " Implies --notemp." + echo " --nomd5 : Don't calculate an MD5 for archive" + echo " --nocrc : Don't calculate a CRC for archive" + echo " --header file : Specify location of the header script" + echo " --follow : Follow the symlinks in the archive" + echo " --nox11 : Disable automatic spawn of a xterm" + echo " --nowait : Do not wait for user input after executing embedded" + echo " program from an xterm" + echo " --lsm file : LSM file describing the package" + echo + echo "Do not forget to give a fully qualified startup script name" + echo "(i.e. with a ./ prefix if inside the archive)." + exit 1 +} + +# Default settings +if type gzip 2>&1 > /dev/null; then + COMPRESS=gzip +else + COMPRESS=Unix +fi +KEEP=n +CURRENT=n +NOX11=n +APPEND=n +COPY=none +TAR_ARGS=cvf +HEADER=`dirname $0`/makeself-header.sh + +# LSM file stuff +LSM_CMD="echo No LSM. >> \"\$archname\"" + +while true +do + case "$1" in + --version | -v) + echo Makeself version $MS_VERSION + exit 0 + ;; + --bzip2) + COMPRESS=bzip2 + shift + ;; + --gzip) + COMPRESS=gzip + shift + ;; + --compress) + COMPRESS=Unix + shift + ;; + --encrypt) + COMPRESS=gpg + shift + ;; + --nocomp) + COMPRESS=none + shift + ;; + --notemp) + KEEP=y + shift + ;; + --copy) + COPY=copy + shift + ;; + --current) + CURRENT=y + KEEP=y + shift + ;; + --header) + HEADER="$2" + shift 2 + ;; + --follow) + TAR_ARGS=cvfh + shift + ;; + --nox11) + NOX11=y + shift + ;; + --nowait) + shift + ;; + --nomd5) + NOMD5=y + shift + ;; + --nocrc) + NOCRC=y + shift + ;; + --append) + APPEND=y + shift + ;; + --lsm) + LSM_CMD="cat \"$2\" >> \"\$archname\"" + shift 2 + ;; + -h | --help) + MS_Usage + ;; + -*) + echo Unrecognized flag : "$1" + MS_Usage + ;; + *) + break + ;; + esac +done + +if test $# -lt 1; then + MS_Usage +else + if test -d "$1"; then + archdir="$1" + else + echo "Directory $1 does not exist." + exit 1 + fi +fi +archname="$2" + +if test "$APPEND" = y; then + if test $# -lt 2; then + MS_Usage + fi + + # Gather the info from the original archive + OLDENV=`sh "$archname" --dumpconf` + if test $? -ne 0; then + echo "Unable to update archive: $archname" >&2 + exit 1 + else + eval "$OLDENV" + fi +else + if test "$KEEP" = n -a $# = 3; then + echo "ERROR: Making a temporary archive with no embedded command does not make sense!" >&2 + echo + MS_Usage + fi + # We don't really want to create an absolute directory... + if test "$CURRENT" = y; then + archdirname="." + else + archdirname=`basename "$1"` + fi + + if test $# -lt 3; then + MS_Usage + fi + + LABEL="$3" + SCRIPT="$4" + test x$SCRIPT = x || shift 1 + shift 3 + SCRIPTARGS="$*" +fi + +if test "$KEEP" = n -a "$CURRENT" = y; then + echo "ERROR: It is A VERY DANGEROUS IDEA to try to combine --notemp and --current." >&2 + exit 1 +fi + +case $COMPRESS in +gzip) + GZIP_CMD="gzip -c9" + GUNZIP_CMD="gzip -cd" + ;; +bzip2) + GZIP_CMD="bzip2 -9" + GUNZIP_CMD="bzip2 -d" + ;; +gpg) + GZIP_CMD="gpg -ac -z9" + GUNZIP_CMD="gpg -d" + ;; +Unix) + GZIP_CMD="compress -cf" + GUNZIP_CMD="exec 2>&-; uncompress -c || test \\\$? -eq 2 || gzip -cd" + ;; +none) + GZIP_CMD="cat" + GUNZIP_CMD="cat" + ;; +esac + +tmpfile="${TMPDIR:=/tmp}/mkself$$" + +if test -f $HEADER; then + oldarchname="$archname" + archname="$tmpfile" + # Generate a fake header to count its lines + SKIP=0 + . $HEADER + SKIP=`cat "$tmpfile" |wc -l` + # Get rid of any spaces + SKIP=`expr $SKIP` + rm -f "$tmpfile" + echo Header is $SKIP lines long >&2 + + archname="$oldarchname" +else + echo "Unable to open header file: $HEADER" >&2 + exit 1 +fi + +echo + +if test "$APPEND" = n; then + if test -f "$archname"; then + echo "WARNING: Overwriting existing file: $archname" >&2 + fi +fi + +USIZE=`du -ks $archdir | cut -f1` +DATE=`LC_ALL=C date` + +if test "." = "$archdirname"; then + if test "$KEEP" = n; then + archdirname="makeself-$$-`date +%Y%m%d%H%M%S`" + fi +fi + +test -d "$archdir" || { echo "Error: $archdir does not exist."; rm -f "$tmpfile"; exit 1; } +echo About to compress $USIZE KB of data... +echo Adding files to archive named \"$archname\"... +exec 3<> "$tmpfile" +(cd "$archdir" && ( tar $TAR_ARGS - . | eval "$GZIP_CMD" >&3 ) ) || { echo Aborting: Archive directory not found or temporary file: "$tmpfile" could not be created.; exec 3>&-; rm -f "$tmpfile"; exit 1; } +exec 3>&- # try to close the archive + +fsize=`cat "$tmpfile" | wc -c | tr -d " "` + +# Compute the checksums + +md5sum=00000000000000000000000000000000 +crcsum=0000000000 + +if test "$NOCRC" = y; then + echo "skipping crc at user request" +else + crcsum=`cat "$tmpfile" | CMD_ENV=xpg4 cksum | sed -e 's/ /Z/' -e 's/ /Z/' | cut -dZ -f1` + echo "CRC: $crcsum" +fi + +if test "$NOMD5" = y; then + echo "skipping md5sum at user request" +else + # Try to locate a MD5 binary + OLD_PATH=$PATH + PATH=${GUESS_MD5_PATH:-"$OLD_PATH:/bin:/usr/bin:/sbin:/usr/local/ssl/bin:/usr/local/bin:/opt/openssl/bin"} + MD5_ARG="" + MD5_PATH=`exec <&- 2>&-; which md5sum || type md5sum` + test -x $MD5_PATH || MD5_PATH=`exec <&- 2>&-; which md5 || type md5` + test -x $MD5_PATH || MD5_PATH=`exec <&- 2>&-; which digest || type digest` + PATH=$OLD_PATH + if test `basename $MD5_PATH` = digest; then + MD5_ARG="-a md5" + fi + if test -x "$MD5_PATH"; then + md5sum=`cat "$tmpfile" | eval "$MD5_PATH $MD5_ARG" | cut -b-32`; + echo "MD5: $md5sum" + else + echo "MD5: none, MD5 command not found" + fi +fi + +if test "$APPEND" = y; then + mv "$archname" "$archname".bak || exit + + # Prepare entry for new archive + filesizes="$filesizes $fsize" + CRCsum="$CRCsum $crcsum" + MD5sum="$MD5sum $md5sum" + USIZE=`expr $USIZE + $OLDUSIZE` + # Generate the header + . $HEADER + # Append the original data + tail -n +$OLDSKIP "$archname".bak >> "$archname" + # Append the new data + cat "$tmpfile" >> "$archname" + + chmod +x "$archname" + rm -f "$archname".bak + echo Self-extractible archive \"$archname\" successfully updated. +else + filesizes="$fsize" + CRCsum="$crcsum" + MD5sum="$md5sum" + + # Generate the header + . $HEADER + + # Append the compressed tar data after the stub + echo + cat "$tmpfile" >> "$archname" + chmod +x "$archname" + echo Self-extractible archive \"$archname\" successfully created. +fi +rm -f "$tmpfile" diff --git a/lib/makeself.url b/lib/makeself.url new file mode 100644 index 0000000..7306ccb --- /dev/null +++ b/lib/makeself.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://www.megastep.org/makeself/ diff --git a/lib/mkselfinst b/lib/mkselfinst new file mode 100755 index 0000000..4c0358f --- /dev/null +++ b/lib/mkselfinst @@ -0,0 +1,62 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$(dirname "$0")/../ulib/ulib" && +urequire DEFAULTS || +exit 1 +OENC="$UTF8" +MAKESELFDIR="$scriptdir/makeself-2.1.5" + +function display_help() { + uecho "$scriptname: Créer une archive auto-extractible qui installe nutools + +USAGE + $scriptname [options] + +OPTIONS + -o dest + Spécifier le fichier de sortie. Par défaut, il s'agit de + nutools-installer.run + --tmp-archive + Spécifier qu'il s'agit d'une archive temporaire. Cette archive + s'auto-détruit après utilisation." +} + +mode=755 +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -o: dest \ + --tmp-archive tmp_archive \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +srcdir="$(abspath "$scriptdir/..")" +srcname="$(basename "$srcdir")" + +[ -n "$dest" ] || dest="nutools-installer.run" +# si le fichier à générer se trouve dans le répertoire à installer, créer +# l'archive dans le répertoire au-dessus +dest="$(abspath "$dest")" +if [ "$srcdir" == "$dest" -o "${dest#$srcdir/}" != "$dest" ]; then + dest="$(dirname "$srcdir")/$(basename "$dest")" +fi + +if [ -f "$dest" -a -z "$tmp_archive" ]; then + ask_yesno "Voulez-vous remplacer l'archive existante $(ppath "$dest")?" O || exit 0 +fi + +## préparer l'archive +ac_set_tmpdir archivedir +cpnovcs "$srcdir" "$archivedir" + +## créer l'archive +args=("$MAKESELFDIR/makeself.sh" --quiet ${tmp_archive:+--tmp-archive} --nox11 + "$archivedir" "$dest" + "nutools installer" + /bin/sh "./$srcname/uinst.sh" "$srcname") + +estep "Création de l'archive $(ppath "$dest")" +"${args[@]}" + +[ -n "$mode" ] && chmod "$mode" "$dest" + +exit 0 diff --git a/lib/nutoolsrc b/lib/nutoolsrc new file mode 100644 index 0000000..d65d4cc --- /dev/null +++ b/lib/nutoolsrc @@ -0,0 +1,14 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Ce fichier contient des informations à charger systématiquement par nutools, +# et qui doivent prendre la précédence sur toute valeur autodétectée. + +# Ce "système" est-is dans un CHROOT? +#export UTOOLS_CHROOT=1 + +# Si oui, les valeurs suivantes peuvent être forcées pour les données systèmes: +#export UTOOLS_UNAME_SYSTEM=Linux +#export UTOOLS_UNAME_MACHINE=i686 +#export UTOOLS_SYSNAME="linux32 linux" +#export UTOOLS_BITS=32 +#export UTOOLS_MYSYSDIST="debian debianlike" +#export UTOOLS_MYSYSVER="squeeze" diff --git a/lib/profile.d/bash_prompt b/lib/profile.d/bash_prompt new file mode 100644 index 0000000..dbfd0c8 --- /dev/null +++ b/lib/profile.d/bash_prompt @@ -0,0 +1,179 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +if [ -n "$UTOOLS_BASH_PROMPT" ]; then + # PS1 + if [ "$UNAME_SYSTEM" != "Linux" -o "$USER" == "$(id -un)" ]; then + DEFAULT_PS1='\u@\h \w \$ ' + else + DEFAULT_PS1="$USER"'(\u)@\h \w \$ ' + fi + if [ "$UTOOLS_CHROOT" == "1" ]; then + DEFAULT_PS1="[CHROOT] $DEFAULT_PS1" + elif [ -n "$UTOOLS_CHROOT" ]; then + DEFAULT_PS1="[CHROOT:$UTOOLS_CHROOT] $DEFAULT_PS1" + fi + GENERATE_PS1='function __genps1() { +local cvs svn git uinst woinst suffix p="$PWD" +if [ -n "$UTOOLS" ]; then + PS1= + while [ "$p" != "$HOME" -a "$p" != "" ]; do + if [ -z "$cvs" -a -d "$p/CVS" ]; then + PS1="${PS1:+$PS1,}cvs$suffix" + cvs=1 + fi + if [ -z "$svn" -a -d "$p/.svn" ]; then + if [ "$UTOOLS" != "f" ]; then + flag="?svn" + elif [ -n "$(svn status -q --ignore-externals 2>/dev/null)" ]; then + flag="*svn" + else + flag="svn" + fi + PS1="${PS1:+$PS1,}$flag$suffix" + svn=1 + fi + if [ -z "$git" -a -d "$p/.git" ]; then + if [ "$UTOOLS" != "f" ]; then + flag="?git" + elif [ -n "$(git status -s 2>/dev/null)" ]; then + flag="*git" + else + flag="git" + fi + if [ "$UTOOLS" == "f" ]; then + branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" + [ "$branch" != "master" ] && flag="$flag:$branch" + fi + if [ -n "$NOPUSH" ]; then + flag="$flag,nopush" + fi + PS1="${PS1:+$PS1,}$flag" + git=1 + fi + if [ -z "$uinst" ]; then + if [ -f "$p/.uinst.conf" ]; then + PS1="${PS1:+$PS1,}uinst$suffix" + uinst=1 + fi + if [ -f "$p/.udir" ]; then + if grep -q "^udir_types=.*uinst" "$p/.udir"; then + PS1="${PS1:+$PS1,}uinst$suffix" + uinst=1 + fi + fi + fi + if [ -z "$woinst" ]; then + if [[ "${p##*/}" == *.framework ]]; then + PS1="${PS1:+$PS1,}woinst$suffix" + woinst=1 + fi + if [[ "${p##*/}" == *.woa ]]; then + PS1="${PS1:+$PS1,}woinst$suffix" + woinst=1 + fi + fi + suffix="/..."; p="${p%/*}" + done + PS1="${PS1:+[$PS1] }$DEFAULT_PS1" +else + PS1="$DEFAULT_PS1" +fi +}; __genps1; unset -f __genps1' + + # PROMPT_COMMAND + if [ "$UNAME_SYSTEM" == "Darwin" ]; then + # Cas particuliers + [ "$TERM" == "vt100" ] && TERM=xterm + fi + + case "$TERM" in + xterm*|rxvt|Eterm|eterm) + PROMPT_COMMAND=' +if [ -n "$UTOOLS" ]; then + echo -ne "\033]0;${USER}@${HOSTNAME%%.*}:${PWD/$HOME/~} $( + if [ -f build.properties ]; then + if grep -q "^project\(_\|\.\)name=" build.properties; then + if grep -q "^project\.name=" build.properties; then + echo -n "[$(sed -ne "/^project\.name=/{s/^project\.name=// +p +}" build.properties)" + elif grep -q "^project_name=" build.properties; then + echo -n "[$(sed -ne "/^project_name=/{s/^project_name=// +p +}" build.properties)" + fi + if [ -f VERSION.txt ]; then + echo -n "-$(sed -ne "1{s/-r.*\$// +p +}" VERSION.txt)" + fi + echo -n "]" + fi + fi)\007" +fi +eval "$GENERATE_PS1" +' + ;; + screen) + PROMPT_COMMAND=' +if [ -n "$UTOOLS" ]; then + echo -ne "\033_[screen] ${USER}@${HOSTNAME%%.*}:${PWD/$HOME/~} $(if [ -f build.properties ]; then + if grep -q "^project\(_\|\.\)name=" build.properties; then + if grep -q "^project\.name=" build.properties; then + echo -n "[$(sed -ne "/^project\.name=/{s/^project\.name=// +p +}" build.properties)" + elif grep -q "^project_name=" build.properties; then + echo -n "[$(sed -ne "/^project_name=/{s/^project_name=// +p +}" build.properties)" + fi + if [ -f VERSION.txt ]; then + echo -n "-$(sed -ne "1{s/-r.*\$// +p +}" VERSION.txt)" + fi + echo -n "]" + fi + fi)\033\\" +fi +eval "$GENERATE_PS1" +' + ;; + *) + PROMPT_COMMAND=' +if [ -n "$UTOOLS" ]; then + if [ -f build.properties ]; then + if grep -q "^project\(_\|\.\)name=" build.properties; then + if grep -q "^project\.name=" build.properties; then + echo -n "\033[34m[$(sed -ne "/^project\.name=/{s/^project\.name=// +p +}" build.properties)" + elif grep -q "^project_name=" build.properties; then + echo -n "\033[34m[$(sed -ne "/^project_name=/{s/^project_name=// +p +}" build.properties)" + fi + if [ -f VERSION.txt ]; then + echo -n "-$(sed -ne "1{s/-r.*\$// +p +}" VERSION.txt)" + fi + echo -e "]\033[0m" + fi + fi +fi +eval "$GENERATE_PS1" +' + ;; + esac + + UDIR_VISITED_DIRS=: + PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND +}"'if [ -f .udir -a "${UDIR_VISITED_DIRS//:$PWD:/}" == "$UDIR_VISITED_DIRS" ]; then + udir --show-note + UDIR_VISITED_DIRS="$UDIR_VISITED_DIRS$PWD:" +fi' + + export PS1 DEFAULT_PS1 GENERATE_PS1 PROMPT_COMMAND UDIR_VISITED_DIRS +fi diff --git a/lib/profile.d/bin_in_path b/lib/profile.d/bin_in_path new file mode 100644 index 0000000..a904a0f --- /dev/null +++ b/lib/profile.d/bin_in_path @@ -0,0 +1,6 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +if [ -n "$UTOOLS_ADD_BIN_IN_PATH" -a "$HOME" != "/" ]; then + mkdir -p "$HOME/bin" + __uaddpath "$HOME/bin" +fi diff --git a/lib/profile.d/dbus b/lib/profile.d/dbus new file mode 100644 index 0000000..bcda4cb --- /dev/null +++ b/lib/profile.d/dbus @@ -0,0 +1,8 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# désactivé pour le moment parce que provoque des effets de bord très gênants: +# les caractères sont droppés une fois sur 2, et il est impossible de se servir +# de la console ssh +#if [ -x /usr/bin/dbus-launch -a -z "$DBUS_SESSION_BUS_ADDRESS" ]; then +# eval "$(/usr/bin/dbus-launch --sh-syntax --exit-with-session)" +#fi diff --git a/lib/profile.d/histcontrol b/lib/profile.d/histcontrol new file mode 100644 index 0000000..4d5b50b --- /dev/null +++ b/lib/profile.d/histcontrol @@ -0,0 +1,2 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +HISTCONTROL=ignoreboth:erasedups diff --git a/lib/profile.d/local_bin_in_path.[Darwin] b/lib/profile.d/local_bin_in_path.[Darwin] new file mode 100644 index 0000000..5bc1cd8 --- /dev/null +++ b/lib/profile.d/local_bin_in_path.[Darwin] @@ -0,0 +1,3 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__uaddpath /usr/local/bin diff --git a/lib/profile.d/nutools b/lib/profile.d/nutools new file mode 100644 index 0000000..1421775 --- /dev/null +++ b/lib/profile.d/nutools @@ -0,0 +1,72 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@before * + +# Faut-il activer les traitements automatiques de utools? Donner à cette +# variable la valeur vide pour désactiver les fonctionnalités potentiellement +# gourmandes en resources. +# Au 18/06/2013, cela ne concerne que les scripts/fichiers suivants: +# - lib/profile.d/bash_prompt +# - ulib/vcs {git_commit} +: "${UTOOLS:=1}" "${NOPUSH:=}" +export UTOOLS NOPUSH + +# fonctions utilitaires pour les chemins +function __udelpath() { + # supprimer le chemin $1 de $2(=PATH) + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; ${2:-PATH}"'="${'"${2:-PATH}"'#$1:}"; '"${2:-PATH}"'="${'"${2:-PATH}"'%:$1}"; '"${2:-PATH}"'="${'"${2:-PATH}"'//:$_qdir:/:}"; [ "$'"${2:-PATH}"'" == "$1" ] && '"${2:-PATH}"'=' +} +function __uaddpath() { + # Ajouter le chemin $1 à la fin, dans $2(=PATH), s'il n'y existe pas déjà + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="${'"${2:-PATH}"':+$'"${2:-PATH}"':}$1"' +} +function __uinspath() { + # Ajouter le chemin $1 au début, dans $2(=PATH), s'il n'y existe pas déjà + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="$1${'"${2:-PATH}"':+:$'"${2:-PATH}"'}"' +} + +# fonctions utilitaires +function pcd() { + local prop dir + prop="${1:-root}" + dir="$("@@dest@@/uproject" "get$prop")" || return 1 + [ -n "$dir" ] && cd "$dir" +} +function utools() { + case "${1:-cycle}" in + 0|d|dis|disable) UTOOLS=;; + 1|e|ena|enable) UTOOLS=1;; + f|full) UTOOLS=f;; + cycle) + case "$UTOOLS" in + "") UTOOLS=1;; + 1) UTOOLS=f;; + f) UTOOLS=;; + esac + ;; + n|np|nopush) NOPUSH=1;; + push) NOPUSH=;; + p|cyclep) + case "$NOPUSH" in + "") NOPUSH=1;; + *) NOPUSH=;; + esac + ;; + esac + echo "UTOOLS=$UTOOLS; NOPUSH=$NOPUSH" 1>&2 +} + +# Configuration +__uaddpath "@@dest@@" PATH +__uaddpath "@@dest@@" UINCPATH +__uaddpath "@@dest@@/legacy" UINCPATH + +[ -n "$UTOOLS_LANG" -a -z "$LANG" ] && LANG="$UTOOLS_LANG" +[ -z "$UTOOLS_LANG" ] && UTOOLS_LANG="$LANG" +export LANG UTOOLS_LANG + +# Le fichier nutoolsrc doit être chargé systématiquement +[ -f /etc/nutoolsrc ] && . /etc/nutoolsrc +[ -f ~/.nutoolsrc ] && . ~/.nutoolsrc diff --git a/lib/profile.d/nutools.userconf b/lib/profile.d/nutools.userconf new file mode 100644 index 0000000..590a43d --- /dev/null +++ b/lib/profile.d/nutools.userconf @@ -0,0 +1,28 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@before nutools + +# Valeur de LANG à forcer +#export UTOOLS_LANG=fr_FR.UTF-8 + +# Valeur de LESSCHARSET s'il faut la forcer. Normalement, less détecte +# automatiquement cette valeur si LANG est spécifié. +#export LESSCHARSET=utf-8 + +# Encoding à utiliser pour la lecture, l'écriture, et l'édition des fichiers +# Si ces valeurs ne sont pas configurées, elles sont autodétectées à partir +# de la valeur de LANG. +#export UTOOLS_OUTPUT_ENCODING=utf-8 +#export UTOOLS_INPUT_ENCODING=utf-8 +#export UTOOLS_EDITOR_ENCODING=utf-8 + +# Désactiver l'affichage en couleur pour les fonctions e* +#export UTOOLS_NO_COLORS=1 + +# Editeur à utiliser +#export EDITOR=vim + +# Ajouter le répertoire ~/bin au PATH +#export UTOOLS_ADD_BIN_IN_PATH=1 + +# Configurer le prompt pour bash +#export UTOOLS_BASH_PROMPT=1 diff --git a/lib/profile.d/proxy b/lib/profile.d/proxy new file mode 100644 index 0000000..5f701ca --- /dev/null +++ b/lib/profile.d/proxy @@ -0,0 +1,58 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function set_proxy() { + if [ -f /etc/uproxy.conf ]; then + source /etc/uproxy.conf + elif [ -z "$http_proxy" -o "$1" == "-f" ]; then + local proxy + local PROXY_LOGIN PROXY_PASSWORD + local HTTP_PROXY_HOST HTTP_PROXY_PORT + local FTP_PROXY_HOST FTP_PROXY_PORT + local PROXY_LOCAL_DOMAINS AUTHFTP_PROXY_HOST + + if [ -x /usr/bin/proxy ]; then + local -a proxies=($(proxy <</dev/null)) + proxy="${proxies[0]}" + if [ "$proxy" == "direct://" ]; then + # pas de proxy + unset http_proxy + unset ftp_proxy + unset no_proxy + return + elif [[ "$proxy" == http://127.0.0.1:* ]]; then + # proxy de self-network + export http_proxy="$proxy" + export ftp_proxy="$proxy" + unset no_proxy + return + fi + unset no_proxy + elif [ -f "$HOME/etc/default/proxy" ]; then + # proxy par défaut + source "$HOME/etc/default/proxy" + + if [ -n "$HTTP_PROXY_HOST" ]; then + proxy="http://$HTTP_PROXY_HOST:${HTTP_PROXY_PORT:-3128}/" + fi + + export no_proxy= + local local_domain + for local_domain in "${PROXY_LOCAL_DOMAINS[@]}"; do + no_proxy="${no_proxy:+$no_proxy,}$local_domain" + done + [ -n "$no_proxy" ] || unset no_proxy + fi + + if [ -n "$proxy" ]; then + if [ -n "$PROXY_LOGIN" ]; then + proxy="${proxy/http:\/\//http://${PROXY_LOGIN}${PROXY_PASSWORD:+:$PROXY_PASSWORD}@}" + fi + export http_proxy="$proxy" + export ftp_proxy="$proxy" + else + unset http_proxy + unset ftp_proxy + fi + fi +} +set_proxy diff --git a/lib/profile.d/runs.userconf b/lib/profile.d/runs.userconf new file mode 100644 index 0000000..33260cc --- /dev/null +++ b/lib/profile.d/runs.userconf @@ -0,0 +1,5 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Répertoire contenant la configuration pour *cet* hôte +# cf aussi la configuration dans ~/etc/default/runs +#RUNSMYHOSTDIR=/etc/runs diff --git a/lib/profile.d/webobjects b/lib/profile.d/webobjects new file mode 100644 index 0000000..e9fec02 --- /dev/null +++ b/lib/profile.d/webobjects @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@after webobjects.userconf + +if [ -z "$NEXT_ROOT" ]; then + case "$UNAME_SYSTEM" in + Linux|SunOS|CYGWIN*|MINGW32*) + [ -d /opt/Apple ] && export NEXT_ROOT=/opt/Apple + ;; + esac +fi diff --git a/lib/profile.d/webobjects.userconf b/lib/profile.d/webobjects.userconf new file mode 100644 index 0000000..159677d --- /dev/null +++ b/lib/profile.d/webobjects.userconf @@ -0,0 +1,7 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Répertoire d'installation de WebObjects, si la valeur est différente de celle +# détectée par défaut. +# Ceci n'est utile que sur Solaris et Linux, car sous MacOS X, NEXT_ROOT= +# Sous les autres systèmes, la valeur par défaut est /opt/Apple +#export NEXT_ROOT=/opt/Apple diff --git a/lib/pywrapper b/lib/pywrapper new file mode 100755 index 0000000..bf28274 --- /dev/null +++ b/lib/pywrapper @@ -0,0 +1,21 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +scriptname="$(basename "$0")" +if [ "$scriptname" == "pywrapper" ]; then + if [ -n "$*" ]; then + nutoolsdir="$(cd "$(dirname "$0")/.."; pwd)" + scriptname="$(basename "$1" .py)" + shift + else + echo "Faire + ln -s /path/to/nutools/SCRIPT /path/to/nutools/lib/pywrapper +pour lancer pyulib/src/uapps/SCRIPT.py" + exit 1 + fi +else + nutoolsdir="$(cd "$(dirname "$0")"; pwd)" +fi + +source "$nutoolsdir/ulib/nutools/pyulib" +exec "$nutoolsdir/pyulib/src/uapps/$scriptname.py" "$@" diff --git a/lib/reptyr/.gitignore b/lib/reptyr/.gitignore new file mode 100644 index 0000000..c857156 --- /dev/null +++ b/lib/reptyr/.gitignore @@ -0,0 +1,3 @@ +reptyr +ptrace +*.o diff --git a/lib/reptyr/COPYING b/lib/reptyr/COPYING new file mode 100644 index 0000000..ca7493d --- /dev/null +++ b/lib/reptyr/COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2011 by Nelson Elhage + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/reptyr/ChangeLog b/lib/reptyr/ChangeLog new file mode 100644 index 0000000..087c0a9 --- /dev/null +++ b/lib/reptyr/ChangeLog @@ -0,0 +1,17 @@ +* 0.4 (Aug 16, 2012) + - Add support for scripting with the -l flag. + - Add a French translation of the man page + - Add a -V flag + +* 0.3 (May 27, 2011) + - Add support for attaching 32-bit programs on x86_64. + - Fix a bug on ARM. + +* 0.2 (Mar 1, 2011) + - Add a man page + - Add a 'make install' target + - Improve detection of which fd's to attach, + - Add a '-s' option to attach programs not currently on a tty. + +* 0.1 (Jan 27, 2011) + - Initial release diff --git a/lib/reptyr/Makefile b/lib/reptyr/Makefile new file mode 100644 index 0000000..8293151 --- /dev/null +++ b/lib/reptyr/Makefile @@ -0,0 +1,23 @@ +override CFLAGS+=-Wall -Werror -D_GNU_SOURCE -g +OBJS=reptyr.o ptrace.o attach.o + +PREFIX=/usr/local + +all: reptyr + +reptyr: $(OBJS) + +attach.o: reptyr.h ptrace.h +reptyr.o: reptyr.h +ptrace.o: ptrace.h $(wildcard arch/*.h) + +clean: + rm -f reptyr $(OBJS) + +install: reptyr + install -d -m 755 $(DESTDIR)$(PREFIX)/bin/ + install -m 755 reptyr $(DESTDIR)$(PREFIX)/bin/reptyr + install -d -m 755 $(DESTDIR)$(PREFIX)/share/man/man1 + install -m 644 reptyr.1 $(DESTDIR)$(PREFIX)/share/man/man1/reptyr.1 + install -d -m 755 $(DESTDIR)$(PREFIX)/share/man/fr/man1 + install -m 644 reptyr.fr.1 $(DESTDIR)$(PREFIX)/share/man/fr/man1/reptyr.1 diff --git a/lib/reptyr/NOTES b/lib/reptyr/NOTES new file mode 100644 index 0000000..3f7d69d --- /dev/null +++ b/lib/reptyr/NOTES @@ -0,0 +1,13 @@ +Attaching: + - Find an fd corresponding to the tty in the child + - Open the new pty in the child + - Copy the termios settings over + - dup() it over the old ones + - Make the new tty the controlling tty: + - Fork a dummy child + - Find all processes in the child's process group. + - For each one, move them to the dummy child's process group + - Make the child setsid() + - Set the terminal as the controlling tty + - Close the newly allocated tty + - Detach diff --git a/lib/reptyr/README.md b/lib/reptyr/README.md new file mode 100644 index 0000000..f68f526 --- /dev/null +++ b/lib/reptyr/README.md @@ -0,0 +1,100 @@ +reptyr - A tool for "re-ptying" programs. +========================================= + +reptyr is a utility for taking an existing running program and +attaching it to a new terminal. Started a long-running process over +ssh, but have to leave and don't want to interrupt it? Just start a +screen, use reptyr to grab it, and then kill the ssh session and head +on home. + +USAGE +----- + + reptyr PID + +"reptyr PID" will grab the process with id PID and attach it to your +current terminal. + +After attaching, the process will take input from and write output to +the new terminal, including ^C and ^Z. (Unfortunately, if you +background it, you will still have to run "bg" or "fg" in the old +terminal. This is likely impossible to fix in a reasonable way without +patching your shell.) + +"But wait, isn't this just screenify?" +-------------------------------------- + +There's a shell script called "screenify" that's been going around the +internet for nigh on 10 years now that uses gdb to (supposedly) +accomplish the same thing. The difference is that reptyr works much, +much, better. + +If you attach a "less" using screenify, it will still take input from +the old terminal. If you attach an ncurses program using screenify, +and resize the window, your program won't notice. If you attach a +process with screenify, ^C in the new terminal won't work. + +reptyr fixes all of these problems, and is the only such tool I know +of that does so. See below for some more details on how it +accomplishes this. + +PORTABILITY +----------- + +reptyr is Linux-only. It uses ptrace to attach to the target and control it at +the syscall level, so it is highly dependent on Linux's particular syscall API, +syscalls, and terminal ioctl()s. A port to Solaris or BSD may be technically +feasible, but would probably require significant re-architecting to abstract out +the platform-specific bits. + +reptyr works on i386, x86_64, and ARM. Ports to other architectures should be +straightforward, and should in most cases be as simple as adding an arch/ARCH.h +file and adding a clause to the ifdef ladder in ptrace.c. + +ptrace_scope on Ubuntu Maverick and up +-------------------------------------- + +`reptyr` depends on the `ptrace` system call to attach to the remote program. On +Ubuntu Maverick and higher, this ability is disabled by default for security +reasons. You can enable it temporarily by doing + + # echo 0 > /proc/sys/kernel/yama/ptrace_scope + +as root, or permanently by editing the file /etc/sysctl.d/10-ptrace.conf, which +also contains more information about exactly what this setting accomplishes. + +reptyr -l +--------- + +As a bonus feature, if you run "reptyr -l", reptyr will create a new +pseudo-terminal pair with nothing attached to the slave end, and print +its name out. + +If you are debugging a program in gdb, you can pass that name to "set +inferior-pty". Because there is no existing program listening to that +tty, this will work much better than passing an existing shell's +terminal. + +How does it work? +----------------- + +The main thing that reptyr does that no one else does is that it +actually changes the controlling terminal of the process you are +attaching. I plan on writing up more about just how this works soon, +but for now, the source is only about 1000 lines if you're curious :) + +PRONUNCIATION +------------- + +I pronounce it like "repeater", but since that's easily ambiguous, +"re-P-T-Y-er" is also acceptable. + + +CREDITS +------- +reptyr was written by Nelson Elhage . Contact him +with any questions or bug reports. + +URL +--- +[http://github.com/nelhage/reptyr]() diff --git a/lib/reptyr/arch/amd64.h b/lib/reptyr/arch/amd64.h new file mode 100644 index 0000000..ee8964f --- /dev/null +++ b/lib/reptyr/arch/amd64.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +//@include x86_common.h + +#define ARCH_HAVE_MULTIPLE_PERSONALITIES + +static struct ptrace_personality arch_personality[2] = { + { + offsetof(struct user, regs.rax), + offsetof(struct user, regs.rdi), + offsetof(struct user, regs.rsi), + offsetof(struct user, regs.rdx), + offsetof(struct user, regs.r10), + offsetof(struct user, regs.r8), + offsetof(struct user, regs.r9), + offsetof(struct user, regs.rip), + }, + { + offsetof(struct user, regs.rax), + offsetof(struct user, regs.rbx), + offsetof(struct user, regs.rcx), + offsetof(struct user, regs.rdx), + offsetof(struct user, regs.rsi), + offsetof(struct user, regs.rdi), + offsetof(struct user, regs.rbp), + offsetof(struct user, regs.rip), + }, +}; + +struct x86_personality x86_personality[2] = { + { + offsetof(struct user, regs.orig_rax), + offsetof(struct user, regs.rax), + }, + { + offsetof(struct user, regs.orig_rax), + offsetof(struct user, regs.rax), + }, +}; + +struct syscall_numbers arch_syscall_numbers[2] = { +//@include default-syscalls.h + { + /* + * These don't seem to be available in any convenient header. We could + * include unistd_32.h, but those definitions would conflict with the + * standard ones. So, let's just hardcode the values for now. Probably + * we should generate this from unistd_32.h during the build process or + * soemthing. + */ + .nr_mmap = 90, + .nr_mmap2 = 192, + .nr_munmap = 91, + .nr_getsid = 147, + .nr_setsid = 66, + .nr_setpgid = 57, + .nr_fork = 2, + .nr_wait4 = 114, + .nr_signal = 48, + .nr_rt_sigaction = 173, + .nr_open = 5, + .nr_close = 6, + .nr_ioctl = 54, + .nr_dup2 = 63 + } +}; + +int arch_get_personality(struct ptrace_child *child) { + unsigned long cs; + + cs = ptrace_command(child, PTRACE_PEEKUSER, + offsetof(struct user, regs.cs)); + if (child->error) + return -1; + if (cs == 0x23) + child->personality = 1; + return 0; +} diff --git a/lib/reptyr/arch/arm.h b/lib/reptyr/arch/arm.h new file mode 100644 index 0000000..3e4320d --- /dev/null +++ b/lib/reptyr/arch/arm.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +static struct ptrace_personality arch_personality[1] = { + { + offsetof(struct user, regs.uregs[0]), + offsetof(struct user, regs.uregs[0]), + offsetof(struct user, regs.uregs[1]), + offsetof(struct user, regs.uregs[2]), + offsetof(struct user, regs.uregs[3]), + offsetof(struct user, regs.uregs[4]), + offsetof(struct user, regs.uregs[5]), + offsetof(struct user, regs.ARM_pc), + } +}; + +static inline void arch_fixup_regs(struct ptrace_child *child) { + child->user.regs.ARM_pc -= 4; +} + +static inline int arch_set_syscall(struct ptrace_child *child, + unsigned long sysno) { + return ptrace_command(child, PTRACE_SET_SYSCALL, 0, sysno); +} + +static inline int arch_save_syscall(struct ptrace_child *child) { + unsigned long swi; + swi = ptrace_command(child, PTRACE_PEEKTEXT, child->user.regs.ARM_pc); + if (child->error) + return -1; + if (swi == 0xef000000) + child->saved_syscall = child->user.regs.uregs[7]; + else + child->saved_syscall = (swi & 0x000fffff); + return 0; +} + +static inline int arch_restore_syscall(struct ptrace_child *child) { + return arch_set_syscall(child, child->saved_syscall); +} diff --git a/lib/reptyr/arch/default-syscalls.h b/lib/reptyr/arch/default-syscalls.h new file mode 100644 index 0000000..28e5c3c --- /dev/null +++ b/lib/reptyr/arch/default-syscalls.h @@ -0,0 +1,32 @@ +#define SC(name) .nr_##name = __NR_##name + +{ +#ifdef __NR_mmap + SC(mmap), +#else + .nr_mmap = -1, +#endif +#ifdef __NR_mmap2 + SC(mmap2), +#else + .nr_mmap2 = -1, +#endif + SC(munmap), + SC(getsid), + SC(setsid), + SC(setpgid), + SC(fork), + SC(wait4), +#ifdef __NR_signal + SC(signal), +#else + .nr_signal = -1, +#endif + SC(rt_sigaction), + SC(open), + SC(close), + SC(ioctl), + SC(dup2), +}, + +#undef SC diff --git a/lib/reptyr/arch/i386.h b/lib/reptyr/arch/i386.h new file mode 100644 index 0000000..7b68d64 --- /dev/null +++ b/lib/reptyr/arch/i386.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +//@include x86_common.h + +static struct ptrace_personality arch_personality[1] = { + { + offsetof(struct user, regs.eax), + offsetof(struct user, regs.ebx), + offsetof(struct user, regs.ecx), + offsetof(struct user, regs.edx), + offsetof(struct user, regs.esi), + offsetof(struct user, regs.edi), + offsetof(struct user, regs.ebp), + offsetof(struct user, regs.eip), + } +}; + +struct x86_personality x86_personality[1] = { + { + offsetof(struct user, regs.orig_eax), + offsetof(struct user, regs.eax), + } +}; diff --git a/lib/reptyr/arch/x86_common.h b/lib/reptyr/arch/x86_common.h new file mode 100644 index 0000000..33f468b --- /dev/null +++ b/lib/reptyr/arch/x86_common.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +struct x86_personality { + size_t orig_ax; + size_t ax; +}; + +struct x86_personality x86_personality[]; + +static inline struct x86_personality *x86_pers(struct ptrace_child *child) { + return &x86_personality[child->personality]; +} + +static inline void arch_fixup_regs(struct ptrace_child *child) { + struct x86_personality *x86pers = x86_pers(child); + struct ptrace_personality *pers = personality(child); + struct user *user = &child->user; +#define ptr(user, off) ((unsigned long*)((void*)(user)+(off))) + *ptr(user, pers->reg_ip) -= 2; + *ptr(user, x86pers->ax) = *ptr(user, x86pers->orig_ax); +} + +static inline int arch_set_syscall(struct ptrace_child *child, + unsigned long sysno) { + return ptrace_command(child, PTRACE_POKEUSER, + x86_pers(child)->orig_ax, + sysno); +} + +static inline int arch_save_syscall(struct ptrace_child *child) { + child->saved_syscall = *ptr(&child->user, x86_pers(child)->orig_ax); + return 0; +} + +static inline int arch_restore_syscall(struct ptrace_child *child) { + return 0; +} + +#undef ptr diff --git a/lib/reptyr/attach.c b/lib/reptyr/attach.c new file mode 100644 index 0000000..c6a8528 --- /dev/null +++ b/lib/reptyr/attach.c @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include "ptrace.h" +//#include "reptyr.h" + +#define TASK_COMM_LENGTH 16 +struct proc_stat { + pid_t pid; + char comm[TASK_COMM_LENGTH+1]; + char state; + pid_t ppid, sid, pgid; + dev_t ctty; +}; + +#define do_syscall(child, name, a0, a1, a2, a3, a4, a5) \ + ptrace_remote_syscall((child), ptrace_syscall_numbers((child))->nr_##name, \ + a0, a1, a2, a3, a4, a5) + +int parse_proc_stat(int statfd, struct proc_stat *out) { + char buf[1024]; + int n; + unsigned dev; + lseek(statfd, 0, SEEK_SET); + if (read(statfd, buf, sizeof buf) < 0) + return errno; + n = sscanf(buf, "%d (%16[^)]) %c %d %d %d %u", + &out->pid, out->comm, + &out->state, &out->ppid, &out->sid, + &out->pgid, &dev); + if (n == EOF) + return errno; + if (n != 7) { + return EINVAL; + } + out->ctty = dev; + return 0; +} + +int read_proc_stat(pid_t pid, struct proc_stat *out) { + char stat_path[PATH_MAX]; + int statfd; + int err; + + snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); + statfd = open(stat_path, O_RDONLY); + if (statfd < 0) { + error("Unable to open %s: %s", stat_path, strerror(errno)); + return -statfd; + } + + err = parse_proc_stat(statfd, out); + close(statfd); + return err; +} + +static void do_unmap(struct ptrace_child *child, child_addr_t addr, unsigned long len) { + if (addr == (unsigned long)-1) + return; + do_syscall(child, munmap, addr, len, 0, 0, 0, 0); +} + +int *get_child_tty_fds(struct ptrace_child *child, int statfd, int *count) { + struct proc_stat child_status; + struct stat tty_st, st; + char buf[PATH_MAX]; + int n = 0, allocated = 0; + int *fds = NULL; + DIR *dir; + struct dirent *d; + int *tmp = NULL; + + debug("Looking up fds for tty in child."); + if ((child->error = parse_proc_stat(statfd, &child_status))) + return NULL; + + debug("Resolved child tty: %x", (unsigned)child_status.ctty); + + if (stat("/dev/tty", &tty_st) < 0) { + child->error = errno; + error("Unable to stat /dev/tty"); + return NULL; + } + + snprintf(buf, sizeof buf, "/proc/%d/fd/", child->pid); + if ((dir = opendir(buf)) == NULL) + return NULL; + while ((d = readdir(dir)) != NULL) { + if (d->d_name[0] == '.') continue; + snprintf(buf, sizeof buf, "/proc/%d/fd/%s", child->pid, d->d_name); + if (stat(buf, &st) < 0) + continue; + + if (st.st_rdev == child_status.ctty + || st.st_rdev == tty_st.st_rdev) { + if (n == allocated) { + allocated = allocated ? 2 * allocated : 2; + tmp = realloc(fds, allocated * sizeof *tmp); + if (tmp == NULL) { + child->error = errno; + error("Unable to allocate memory for fd array."); + free(fds); + fds = NULL; + goto out; + } + fds = tmp; + } + debug("Found an alias for the tty: %s", d->d_name); + fds[n++] = atoi(d->d_name); + } + } + out: + *count = n; + closedir(dir); + return fds; +} + +void move_process_group(struct ptrace_child *child, pid_t from, pid_t to) { + DIR *dir; + struct dirent *d; + pid_t pid; + char *p; + int err; + + if ((dir = opendir("/proc/")) == NULL) + return; + + while ((d = readdir(dir)) != NULL) { + if (d->d_name[0] == '.') continue; + pid = strtol(d->d_name, &p, 10); + if (*p) continue; + if (getpgid(pid) == from) { + debug("Change pgid for pid %d", pid); + err = do_syscall(child, setpgid, pid, to, 0, 0, 0, 0); + if (err < 0) + error(" failed: %s", strerror(-err)); + } + } + closedir(dir); +} + +int do_setsid(struct ptrace_child *child) { + int err = 0; + struct ptrace_child dummy; + + err = do_syscall(child, fork, 0, 0, 0, 0, 0, 0); + if (err < 0) + return err; + + debug("Forked a child: %ld", child->forked_pid); + + err = ptrace_finish_attach(&dummy, child->forked_pid); + if (err < 0) + goto out_kill; + + dummy.state = ptrace_after_syscall; + memcpy(&dummy.user, &child->user, sizeof child->user); + if (ptrace_restore_regs(&dummy)) { + err = dummy.error; + goto out_kill; + } + + err = do_syscall(&dummy, setpgid, 0, 0, 0, 0, 0, 0); + if (err < 0) { + error("Failed to setpgid: %s", strerror(-err)); + goto out_kill; + } + + move_process_group(child, child->pid, dummy.pid); + + err = do_syscall(child, setsid, 0, 0, 0, 0, 0, 0); + if (err < 0) { + error("Failed to setsid: %s", strerror(-err)); + move_process_group(child, dummy.pid, child->pid); + goto out_kill; + } + + debug("Did setsid()"); + + out_kill: + kill(dummy.pid, SIGKILL); + ptrace_detach_child(&dummy); + ptrace_wait(&dummy); + do_syscall(child, wait4, dummy.pid, 0, WNOHANG, 0, 0, 0); + return err; +} + +int ignore_hup(struct ptrace_child *child, unsigned long scratch_page) { + int err; + if (ptrace_syscall_numbers(child)->nr_signal != -1) { + err = do_syscall(child, signal, SIGHUP, (unsigned long)SIG_IGN, 0, 0, 0, 0); + } else { + struct sigaction act = { + .sa_handler = SIG_IGN, + }; + err = ptrace_memcpy_to_child(child, scratch_page, + &act, sizeof act); + if (err < 0) + return err; + err = do_syscall(child, rt_sigaction, + SIGHUP, scratch_page, + 0, 8, 0, 0); + } + return err; +} + +/* + * Wait for the specific pid to enter state 'T', or stopped. We have to pull the + * /proc file rather than attaching with ptrace() and doing a wait() because + * half the point of this exercise is for the process's real parent (the shell) + * to see the TSTP. + * + * In case the process is masking or ignoring SIGTSTP, we time out after a + * second and continue with the attach -- it'll still work mostly right, you + * just won't get the old shell back. + */ +void wait_for_stop(pid_t pid, int fd) { + struct timeval start, now; + struct timespec sleep; + struct proc_stat st; + + gettimeofday(&start, NULL); + while (1) { + gettimeofday(&now, NULL); + if ((now.tv_sec > start.tv_sec && now.tv_usec > start.tv_usec) + || (now.tv_sec - start.tv_sec > 1)) { + error("Timed out waiting for child stop."); + break; + } + /* + * If anything goes wrong reading or parsing the stat node, just give + * up. + */ + if (parse_proc_stat(fd, &st)) + break; + if (st.state == 'T') + break; + + sleep.tv_sec = 0; + sleep.tv_nsec = 10000000; + nanosleep(&sleep, NULL); + } +} + +int copy_tty_state(pid_t pid, const char *pty) { + char buf[PATH_MAX]; + int fd, err = EINVAL; + struct termios tio; + int i; + + for (i = 0; i < 3 && err; i++) { + err = 0; + snprintf(buf, sizeof buf, "/proc/%d/fd/%d", pid, i); + + if ((fd = open(buf, O_RDONLY)) < 0) { + err = -fd; + continue; + } + + if (!isatty(fd)) { + err = ENOTTY; + goto retry; + } + + if (tcgetattr(fd, &tio) < 0) { + err = -errno; + } + retry: + close(fd); + } + + if (err) + return err; + + if ((fd = open(pty, O_RDONLY)) < 0) + return -errno; + + if (tcsetattr(fd, TCSANOW, &tio) < 0) + err = errno; + close(fd); + return -err; +} + +int check_pgroup(pid_t target) { + pid_t pg; + DIR *dir; + struct dirent *d; + pid_t pid; + char *p; + int err = 0; + struct proc_stat pid_stat; + + debug("Checking for problematic process group members..."); + + pg = getpgid(target); + if (pg < 0) { + error("Unable to get pgid (does process %d exist?)", (int)target); + return pg; + } + + if ((dir = opendir("/proc/")) == NULL) + return errno; + + while ((d = readdir(dir)) != NULL) { + if (d->d_name[0] == '.') continue; + pid = strtol(d->d_name, &p, 10); + if (*p) continue; + if (pid == target) continue; + if (getpgid(pid) == pg) { + /* + * We are actually being somewhat overly-conservative here + * -- if pid is a child of target, and has not yet called + * execve(), reptyr's setpgid() strategy may suffice. That + * is a fairly rare case, and annoying to check for, so + * for now let's just bail out. + */ + if ((err = read_proc_stat(pid, &pid_stat))) { + memcpy(pid_stat.comm, "???", 4); + } + error("Process %d (%.*s) shares %d's process group. Unable to attach.\n" + "(This most commonly means that %d has a suprocesses).", + (int)pid, TASK_COMM_LENGTH, pid_stat.comm, (int)target, (int)target); + err = EINVAL; + goto out; + } + } + out: + closedir(dir); + return err; +} + +int attach_child(pid_t pid, const char *pty, int force_stdio) { + struct ptrace_child child; + unsigned long scratch_page = -1; + int *child_tty_fds = NULL, n_fds, child_fd, statfd; + int i; + int err = 0; + long page_size = sysconf(_SC_PAGE_SIZE); + char stat_path[PATH_MAX]; + long mmap_syscall; + + if ((err = check_pgroup(pid))) { + return err; + } + + if ((err = copy_tty_state(pid, pty))) { + if (err == ENOTTY && !force_stdio) { + error("Target is not connected to a terminal.\n" + " Use -s to force attaching anyways."); + return err; + } + } + + snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); + statfd = open(stat_path, O_RDONLY); + if (statfd < 0) { + error("Unable to open %s: %s", stat_path, strerror(errno)); + return -statfd; + } + + kill(pid, SIGTSTP); + wait_for_stop(pid, statfd); + + if (ptrace_attach_child(&child, pid)) { + err = child.error; + goto out_cont; + } + + if (ptrace_advance_to_state(&child, ptrace_at_syscall)) { + err = child.error; + goto out_detach; + } + if (ptrace_save_regs(&child)) { + err = child.error; + goto out_detach; + } + + mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap2; + if (mmap_syscall == -1) + mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap; + scratch_page = ptrace_remote_syscall(&child, mmap_syscall, 0, + page_size, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); + + if (scratch_page > (unsigned long)-1000) { + err = -(signed long)scratch_page; + goto out_unmap; + } + + debug("Allocated scratch page: %lx", scratch_page); + + if (force_stdio) { + child_tty_fds = malloc(3 * sizeof(int)); + if (!child_tty_fds) { + err = ENOMEM; + goto out_unmap; + } + n_fds = 3; + child_tty_fds[0] = 0; + child_tty_fds[1] = 1; + child_tty_fds[2] = 2; + } else { + child_tty_fds = get_child_tty_fds(&child, statfd, &n_fds); + if (!child_tty_fds) { + err = child.error; + goto out_unmap; + } + } + + if (ptrace_memcpy_to_child(&child, scratch_page, pty, strlen(pty)+1)) { + err = child.error; + error("Unable to memcpy the pty path to child."); + goto out_free_fds; + } + + child_fd = do_syscall(&child, open, + scratch_page, O_RDWR|O_NOCTTY, + 0, 0, 0, 0); + if (child_fd < 0) { + err = child_fd; + error("Unable to open the tty in the child."); + goto out_free_fds; + } + + debug("Opened the new tty in the child: %d", child_fd); + + err = ignore_hup(&child, scratch_page); + if (err < 0) + goto out_close; + + err = do_syscall(&child, getsid, 0, 0, 0, 0, 0, 0); + if (err != child.pid) { + debug("Target is not a session leader, attempting to setsid."); + err = do_setsid(&child); + } else { + do_syscall(&child, ioctl, child_tty_fds[0], TIOCNOTTY, 0, 0, 0, 0); + } + if (err < 0) + goto out_close; + + err = do_syscall(&child, ioctl, child_fd, TIOCSCTTY, 0, 0, 0, 0); + if (err < 0) { + error("Unable to set controlling terminal."); + goto out_close; + } + + debug("Set the controlling tty"); + + for (i = 0; i < n_fds; i++) + do_syscall(&child, dup2, child_fd, child_tty_fds[i], 0, 0, 0, 0); + + + err = 0; + + out_close: + do_syscall(&child, close, child_fd, 0, 0, 0, 0, 0); + out_free_fds: + free(child_tty_fds); + + out_unmap: + do_unmap(&child, scratch_page, page_size); + + ptrace_restore_regs(&child); + out_detach: + ptrace_detach_child(&child); + + if (err == 0) { + kill(child.pid, SIGSTOP); + wait_for_stop(child.pid, statfd); + } + kill(child.pid, SIGWINCH); + out_cont: + kill(child.pid, SIGCONT); + close(statfd); + + return err < 0 ? -err : err; +} diff --git a/lib/reptyr/ptrace.c b/lib/reptyr/ptrace.c new file mode 100644 index 0000000..ed9bd73 --- /dev/null +++ b/lib/reptyr/ptrace.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include "ptrace.h" + +/* + * RHEL 5's kernel supports these flags, but their libc doesn't ship a ptrace.h + * that defines them. Define them here, and if our kernel doesn't support them, + * we'll find out when PTRACE_SETOPTIONS fails. + */ +#ifndef PTRACE_O_TRACESYSGOOD +#define PTRACE_O_TRACESYSGOOD 0x00000001 +#endif + +#ifndef PTRACE_O_TRACEFORK +#define PTRACE_O_TRACEFORK 0x00000002 +#endif + +#ifndef PTRACE_EVENT_FORK +#define PTRACE_EVENT_FORK 1 +#endif + +#define min(x, y) ({ \ + typeof(x) _min1 = (x); \ + typeof(y) _min2 = (y); \ + _min1 < _min2 ? _min1 : _min2; }) + +static long __ptrace_command(struct ptrace_child *child, enum __ptrace_request req, + void *, void*); + +#define ptrace_command(cld, req, ...) _ptrace_command(cld, req, ## __VA_ARGS__, NULL, NULL) +#define _ptrace_command(cld, req, addr, data, ...) __ptrace_command((cld), (req), (void*)(addr), (void*)(data)) + + +struct ptrace_personality { + size_t syscall_rv; + size_t syscall_arg0; + size_t syscall_arg1; + size_t syscall_arg2; + size_t syscall_arg3; + size_t syscall_arg4; + size_t syscall_arg5; + size_t reg_ip; +}; + +static struct ptrace_personality *personality(struct ptrace_child *child); + +#if defined(__amd64__) +//@include arch/amd64.h +#elif defined(__i386__) +//@include arch/i386.h +#elif defined(__arm__) +//@include arch/arm.h +#else +#error Unsupported architecture. +#endif + +#ifndef ARCH_HAVE_MULTIPLE_PERSONALITIES +int arch_get_personality(struct ptrace_child *child) { + return 0; +} + +struct syscall_numbers arch_syscall_numbers[] = { +//@include arch/default-syscalls.h +}; +#endif + +static struct ptrace_personality *personality(struct ptrace_child *child) { + return &arch_personality[child->personality]; +} + +struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child) { + return &arch_syscall_numbers[child->personality]; +} + +int ptrace_attach_child(struct ptrace_child *child, pid_t pid) { + memset(child, 0, sizeof *child); + child->pid = pid; + if (ptrace_command(child, PTRACE_ATTACH) < 0) + return -1; + + return ptrace_finish_attach(child, pid); +} + +int ptrace_finish_attach(struct ptrace_child *child, pid_t pid) { + memset(child, 0, sizeof *child); + child->pid = pid; + + kill(pid, SIGCONT); + if (ptrace_wait(child) < 0) + goto detach; + + if (arch_get_personality(child)) + goto detach; + + if (ptrace_command(child, PTRACE_SETOPTIONS, 0, + PTRACE_O_TRACESYSGOOD|PTRACE_O_TRACEFORK) < 0) + goto detach; + + return 0; + + detach: + /* Don't clobber child->error */ + ptrace(PTRACE_DETACH, child->pid, 0, 0); + return -1; +} + +int ptrace_detach_child(struct ptrace_child *child) { + if (ptrace_command(child, PTRACE_DETACH, 0, 0) < 0) + return -1; + child->state = ptrace_detached; + return 0; +} + +int ptrace_wait(struct ptrace_child *child) { + if (waitpid(child->pid, &child->status, 0) < 0) { + child->error = errno; + return -1; + } + if (WIFEXITED(child->status) || WIFSIGNALED(child->status)) { + child->state = ptrace_exited; + } else if (WIFSTOPPED(child->status)) { + int sig = WSTOPSIG(child->status); + if (sig & 0x80) { + child->state = (child->state == ptrace_at_syscall) ? + ptrace_after_syscall : ptrace_at_syscall; + } else { + if (sig == SIGTRAP && (((child->status >> 8) & PTRACE_EVENT_FORK) == PTRACE_EVENT_FORK)) + ptrace_command(child, PTRACE_GETEVENTMSG, 0, &child->forked_pid); + if (child->state != ptrace_at_syscall) + child->state = ptrace_stopped; + } + } else { + child->error = EINVAL; + return -1; + } + return 0; +} + +int ptrace_advance_to_state(struct ptrace_child *child, + enum child_state desired) { + int err; + while (child->state != desired) { + switch(desired) { + case ptrace_after_syscall: + case ptrace_at_syscall: + if (WIFSTOPPED(child->status) && WSTOPSIG(child->status) == SIGSEGV) { + child->error = EAGAIN; + return -1; + } + err = ptrace_command(child, PTRACE_SYSCALL, 0, 0); + break; + case ptrace_running: + return ptrace_command(child, PTRACE_CONT, 0, 0); + case ptrace_stopped: + err = kill(child->pid, SIGSTOP); + if (err < 0) + child->error = errno; + break; + default: + child->error = EINVAL; + return -1; + } + if (err < 0) + return err; + if (ptrace_wait(child) < 0) + return -1; + } + return 0; +} + + +int ptrace_save_regs(struct ptrace_child *child) { + if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) + return -1; + if (ptrace_command(child, PTRACE_GETREGS, 0, &child->user) < 0) + return -1; + arch_fixup_regs(child); + if (arch_save_syscall(child) < 0) + return -1; + return 0; +} + +int ptrace_restore_regs(struct ptrace_child *child) { + int err; + err = ptrace_command(child, PTRACE_SETREGS, 0, &child->user); + if (err < 0) + return err; + return arch_restore_syscall(child); +} + +unsigned long ptrace_remote_syscall(struct ptrace_child *child, + unsigned long sysno, + unsigned long p0, unsigned long p1, + unsigned long p2, unsigned long p3, + unsigned long p4, unsigned long p5) { + unsigned long rv; + if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) + return -1; + +#define setreg(r, v) do { \ + if (ptrace_command(child, PTRACE_POKEUSER, \ + personality(child)->r, \ + (v)) < 0) \ + return -1; \ + } while (0) + + if (arch_set_syscall(child, sysno) < 0) + return -1; + setreg(syscall_arg0, p0); + setreg(syscall_arg1, p1); + setreg(syscall_arg2, p2); + setreg(syscall_arg3, p3); + setreg(syscall_arg4, p4); + setreg(syscall_arg5, p5); + + if (ptrace_advance_to_state(child, ptrace_after_syscall) < 0) + return -1; + + rv = ptrace_command(child, PTRACE_PEEKUSER, + personality(child)->syscall_rv); + if (child->error) + return -1; + + setreg(reg_ip, *(unsigned long*)((void*)&child->user + + personality(child)->reg_ip)); + + #undef setreg + + return rv; +} + +int ptrace_memcpy_to_child(struct ptrace_child *child, child_addr_t dst, const void *src, size_t n) { + unsigned long scratch; + + while (n >= sizeof(unsigned long)) { + if (ptrace_command(child, PTRACE_POKEDATA, dst, *((unsigned long*)src)) < 0) + return -1; + dst += sizeof(unsigned long); + src += sizeof(unsigned long); + n -= sizeof(unsigned long); + } + + if (n) { + scratch = ptrace_command(child, PTRACE_PEEKDATA, dst); + if (child->error) + return -1; + memcpy(&scratch, src, n); + if (ptrace_command(child, PTRACE_POKEDATA, dst, scratch) < 0) + return -1; + } + + return 0; +} + +int ptrace_memcpy_from_child(struct ptrace_child *child, void *dst, child_addr_t src, size_t n) { + unsigned long scratch; + + while (n) { + scratch = ptrace_command(child, PTRACE_PEEKDATA, src); + if (child->error) return -1; + memcpy(dst, &scratch, min(n, sizeof(unsigned long))); + + dst += sizeof(unsigned long); + src += sizeof(unsigned long); + if (n >= sizeof(unsigned long)) + n -= sizeof(unsigned long); + else + n = 0; + } + return 0; +} + +static long __ptrace_command(struct ptrace_child *child, enum __ptrace_request req, + void *addr, void *data) { + long rv; + errno = 0; + rv = ptrace(req, child->pid, addr, data); + child->error = errno; + return rv; +} + + +#ifdef BUILD_PTRACE_MAIN +int main(int argc, char **argv) { + struct ptrace_child child; + pid_t pid; + + if (argc < 2) { + printf("Usage: %s pid\n", argv[0]); + return 1; + } + pid = atoi(argv[1]); + + assert(!ptrace_attach_child(&child, pid)); + assert(!ptrace_save_regs(&child)); + + printf("mmap = %lx\n", ptrace_remote_syscall(&child, mmap_syscall, 0, + 4096, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, 0, 0)); + + reset_user_struct(&child.user); + assert(!ptrace_restore_regs(&child)); + assert(!ptrace_detach_child(&child)); + + return 0; +} +#endif diff --git a/lib/reptyr/ptrace.h b/lib/reptyr/ptrace.h new file mode 100644 index 0000000..d981369 --- /dev/null +++ b/lib/reptyr/ptrace.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include + +enum child_state { + ptrace_detached = 0, + ptrace_at_syscall, + ptrace_after_syscall, + ptrace_running, + ptrace_stopped, + ptrace_exited +}; + +struct ptrace_child { + pid_t pid; + enum child_state state; + int personality; + int status; + int error; + unsigned long forked_pid; + struct user user; + unsigned long saved_syscall; +}; + +struct syscall_numbers { + long nr_mmap; + long nr_mmap2; + long nr_munmap; + long nr_getsid; + long nr_setsid; + long nr_setpgid; + long nr_fork; + long nr_wait4; + long nr_signal; + long nr_rt_sigaction; + long nr_open; + long nr_close; + long nr_ioctl; + long nr_dup2; +}; + +typedef unsigned long child_addr_t; + +int ptrace_wait(struct ptrace_child *child); +int ptrace_attach_child(struct ptrace_child *child, pid_t pid); +int ptrace_finish_attach(struct ptrace_child *child, pid_t pid); +int ptrace_detach_child(struct ptrace_child *child); +int ptrace_wait(struct ptrace_child *child); +int ptrace_advance_to_state(struct ptrace_child *child, + enum child_state desired); +int ptrace_save_regs(struct ptrace_child *child); +int ptrace_restore_regs(struct ptrace_child *child); +unsigned long ptrace_remote_syscall(struct ptrace_child *child, + unsigned long sysno, + unsigned long p0, unsigned long p1, + unsigned long p2, unsigned long p3, + unsigned long p4, unsigned long p5); + +int ptrace_memcpy_to_child(struct ptrace_child *, child_addr_t, const void*, size_t); +int ptrace_memcpy_from_child(struct ptrace_child *, void*, child_addr_t, size_t); +struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child); diff --git a/lib/reptyr/reptyr.1 b/lib/reptyr/reptyr.1 new file mode 100644 index 0000000..037b363 --- /dev/null +++ b/lib/reptyr/reptyr.1 @@ -0,0 +1,164 @@ +.mso www.tmac +.TH reptyr 1 "03 Feb 2011" +.SH NAME +reptyr \- Reparent a running program to a new terminal +.SH SYNOPSIS +.B reptyr +.I PID + +.B reptyr \-l|\-L [COMMAND [ARGS]] + +.SH DESCRIPTION + +.B reptyr +is a utility for taking an existing running program and +attaching it to a new terminal. Started a long-running process over +ssh, but have to leave and don't want to interrupt it? Just start a +screen, use +.B reptyr +to grab it, and then kill the ssh session and head +on home. +.LP +.B reptyr +works by attaching to the target program using +.BR ptrace (2), +redirecting relevant file descriptors, and changing the program's controlling +terminal (See +.BR tty (4)) +It is this last detail that makes +.B reptyr +work much better than alternatives such as +.BR retty (1). + +.LP +After attaching a program, the program will appear to be either backgrounded or +suspended to the shell it was launched from (depending on the shell). For +maximal safety you can run +.IP +bg; disown +.LP + +in the old shell to remove the association with the program, but +.B reptyr +will attempt to ensure that the target program remains running even if you close +the shell without doing so. + +.SH OPTIONS + +.B \-l, \-L [COMMAND [ARGS]] +.IP +Instead of attaching to a new process, create a new pty pair, proxy the master +end to the current terminal, and then print the name of the slave pty. This can +be passed to e.g. +.B gdb\'s +.I set inferior-tty +option. + +If an optional +.B COMMAND +and +.B ARGS +are passed in conjunction with +.B -l, +that command will be executed as a child of +.B reptyr +with the +.B REPTYR_PTY +environment variable set to the name of the slave pty. If +.B -L +is used instead of +.B -l, +then fds 0-2 of the child will also be redirected to point to the +slave, and the child will be run in a fresh session with the slave as +its controlling terminal. +.LP + +.B \-s +.IP + +By default, reptyr will move any file descriptors in the target that were +connected to the target's controlling terminal to point to the new terminal. The +.B -s +option will cause reptyr to unconditionally attach file descriptors 0, 1, and 2 +in the target, even if the target has no controlling terminal or they are not +connected to a terminal. +.LP + +.B \-v +.IP +Print the version of +.B reptyr +and exit. +.LP + +.B \-h +.IP +Print a usage message and exit. +.LP + +.B \-V +.IP +Print verbose debug output while running. +.LP + +.SH NOTES + +.B reptyr +depends on the +.BR ptrace (2) +system call to attach to the remote program. On Ubuntu Maverick and higher, this +ability is disabled by default for security reasons. You can enable it +temporarily by doing +.IP + # echo 0 > /proc/sys/kernel/yama/ptrace_scope +.LP +as root, or permanently by editing the file +.IR /etc/sysctl.d/10-ptrace.conf , +which also contains more information about this setting. + +.SH BUGS + +When attaching to some curses programs, they will not redraw the screen right +away, and a +.B ^L +or similar will be needed to force a redraw. + +Similarly, after attaching to certain programs, the old terminal will be left in +an odd state, and a +.B clear +or even +.B reset +may be required before the old terminal is usable again. + +Attaching to rtorrent (and probably some other apps) doesn't work right +(rtorrent stops accepting input) (The problem is that rtorrent is using epoll to +poll stdin, and we don't update the internal reference that the epoll fd has to +the old tty). + +Attaching to a process with children doesn't work right. This should be possible +to fix -- I just need to ptrace each child individually and do the same games to +it. + +Attaching a +.BR less (1) +process doesn't work if you have a +.I .lessfilter +file, as +.BR less +leaves around a zombie child in this case. This could be worked around. + +Bugs should be reported to the author (see below) or via the issue tracker on +GitHub. + +.SH AUTHORS + +reptyr was written by Nelson Elhage . + +.SH HOMEPAGE + +.URL https://github.com/nelhage/reptyr + +.SH SEE ALSO + +.BR neercs (1), +.BR screen (1) diff --git a/lib/reptyr/reptyr.c b/lib/reptyr/reptyr.c new file mode 100644 index 0000000..e6dc4d1 --- /dev/null +++ b/lib/reptyr/reptyr.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include "reptyr.h" + +#ifndef __linux__ +#error reptyr is currently Linux-only. +#endif + +static int verbose = 0; + +void _debug(const char *pfx, const char *msg, va_list ap) { + + if (pfx) + fprintf(stderr, "%s", pfx); + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); +} + +void die(const char *msg, ...) { + va_list ap; + va_start(ap, msg); + _debug("[!] ", msg, ap); + va_end(ap); + + exit(1); +} + +void debug(const char *msg, ...) { + + va_list ap; + + if (!verbose) + return; + + va_start(ap, msg); + _debug("[+] ", msg, ap); + va_end(ap); +} + +void error(const char *msg, ...) { + va_list ap; + va_start(ap, msg); + _debug("[-] ", msg, ap); + va_end(ap); +} + +void setup_raw(struct termios *save) { + struct termios set; + if (tcgetattr(0, save) < 0) + die("Unable to read terminal attributes: %m"); + set = *save; + cfmakeraw(&set); + if (tcsetattr(0, TCSANOW, &set) < 0) + die("Unable to set terminal attributes: %m"); +} + +void resize_pty(int pty) { + struct winsize sz; + if (ioctl(0, TIOCGWINSZ, &sz) < 0) + return; + ioctl(pty, TIOCSWINSZ, &sz); +} + +int writeall(int fd, const void *buf, ssize_t count) { + ssize_t rv; + while (count > 0) { + rv = write(fd, buf, count); + if (rv < 0) { + if (errno == EINTR) + continue; + return rv; + } + count -= rv; + buf += rv; + } + return 0; +} + +volatile sig_atomic_t winch_happened = 0; + +void do_winch(int signal) { + winch_happened = 1; +} + +void do_proxy(int pty) { + char buf[4096]; + ssize_t count; + fd_set set; + while (1) { + if (winch_happened) { + winch_happened = 0; + /* + * FIXME: If a signal comes in after this point but before + * select(), the resize will be delayed until we get more + * input. signalfd() is probably the cleanest solution. + */ + resize_pty(pty); + } + FD_ZERO(&set); + FD_SET(0, &set); + FD_SET(pty, &set); + if (select(pty+1, &set, NULL, NULL, NULL) < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "select: %m"); + return; + } + if (FD_ISSET(0, &set)) { + count = read(0, buf, sizeof buf); + if (count < 0) + return; + writeall(pty, buf, count); + } + if (FD_ISSET(pty, &set)) { + count = read(pty, buf, sizeof buf); + if (count < 0) + return; + writeall(1, buf, count); + } + } +} + +void usage(char *me) { + fprintf(stderr, "Usage: %s [-s] PID\n", me); + fprintf(stderr, " %s -l|-L [COMMAND [ARGS]]\n", me); + fprintf(stderr, " -l Create a new pty pair and print the name of the slave.\n"); + fprintf(stderr, " if there are command-line arguments after -l\n"); + fprintf(stderr, " they are executed with REPTYR_PTY set to path of pty.\n"); + fprintf(stderr, " -L Like '-l', but also redirect the child's stdio to the slave.\n"); + fprintf(stderr, " -s Attach fds 0-2 on the target, even if it is not attached to a tty.\n"); + fprintf(stderr, " -h Print this help message and exit.\n"); + fprintf(stderr, " -v Print the version number and exit.\n"); + fprintf(stderr, " -V Print verbose debug output.\n"); +} + +void check_yama_ptrace_scope(void) { + int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY); + if (fd >= 0) { + char buf[256]; + int n; + n = read(fd, buf, sizeof buf); + close(fd); + if (n > 0) { + if (!atoi(buf)) { + return; + } + } + } else if (errno == ENOENT) + return; + fprintf(stderr, "The kernel denied permission while attaching. If your uid matches\n"); + fprintf(stderr, "the target's, check the value of /proc/sys/kernel/yama/ptrace_scope.\n"); + fprintf(stderr, "For more information, see /etc/sysctl.d/10-ptrace.conf\n"); +} + +int main(int argc, char **argv) { + struct termios saved_termios; + struct sigaction act; + int pty; + int arg = 1; + int do_attach = 1; + int force_stdio = 0; + int unattached_script_redirection = 0; + + if (argc < 2) { + usage(argv[0]); + return 2; + } + if (argv[arg][0] == '-') { + switch(argv[arg][1]) { + case 'h': + usage(argv[0]); + return 0; + case 'l': + do_attach = 0; + break; + case 'L': + do_attach = 0; + unattached_script_redirection = 1; + break; + case 's': + arg++; + force_stdio = 1; + break; + case 'v': + printf("This is reptyr version %s.\n", REPTYR_VERSION); + printf(" by Nelson Elhage \n"); + printf("http://github.com/nelhage/reptyr/\n"); + return 0; + case 'V': + arg++; + verbose = 1; + break; + default: + usage(argv[0]); + return 1; + } + } + + if (do_attach && arg >= argc) { + fprintf(stderr, "%s: No pid specified to attach\n", argv[0]); + usage(argv[0]); + return 1; + } + + if ((pty = open("/dev/ptmx", O_RDWR|O_NOCTTY)) < 0) + die("Unable to open /dev/ptmx: %m"); + if (unlockpt(pty) < 0) + die("Unable to unlockpt: %m"); + if (grantpt(pty) < 0) + die("Unable to grantpt: %m"); + + if (do_attach) { + pid_t child = atoi(argv[arg]); + int err; + if ((err = attach_child(child, ptsname(pty), force_stdio))) { + fprintf(stderr, "Unable to attach to pid %d: %s\n", child, strerror(err)); + if (err == EPERM) { + check_yama_ptrace_scope(); + } + return 1; + } + } else { + printf("Opened a new pty: %s\n", ptsname(pty)); + fflush(stdout); + if (argc > 2) { + if(!fork()) { + setenv("REPTYR_PTY", ptsname(pty), 1); + if (unattached_script_redirection) { + int f; + setpgid(0, getppid()); + setsid(); + f = open(ptsname(pty), O_RDONLY, 0); dup2(f, 0); close(f); + f = open(ptsname(pty), O_WRONLY, 0); dup2(f, 1); dup2(f,2); close(f); + } + close(pty); + execvp(argv[2], argv+2); + exit(1); + } + } + } + + setup_raw(&saved_termios); + memset(&act, 0, sizeof act); + act.sa_handler = do_winch; + act.sa_flags = 0; + sigaction(SIGWINCH, &act, NULL); + resize_pty(pty); + do_proxy(pty); + tcsetattr(0, TCSANOW, &saved_termios); + + return 0; +} diff --git a/lib/reptyr/reptyr.fr.1 b/lib/reptyr/reptyr.fr.1 new file mode 100644 index 0000000..3bf82d5 --- /dev/null +++ b/lib/reptyr/reptyr.fr.1 @@ -0,0 +1,155 @@ +.\" Traduction Laurent GAUTROT - 2011-08-06 +.mso www.tmac +.TH reptyr 1 "03 Feb 2011" +.SH NOM +reptyr \- Reassoccie un programme en cours d'exécution à un nouveau terminal +.SH SYNOPSIS +.B reptyr +.I PID + +.B reptyr \-l + +.SH DESCRIPTION + +.B reptyr +est un utilitaire qui prend un programme en cours d'exécution et +l'attache à un nouveau terminal. Vous avez démarré un programme long à +travers ssh, mais vous devez partir et vous ne voulez pas +l'interrompre\ ? Démarrez simplement un screen, utilisez +.B reptyr +pour l'attraper, puis tuez la session ssh et vous pouvez rentrer à la +maison. +.LP +.B reptyr +fonctionne en s'attachant au programme visé à l'aide de +.BR ptrace (2), +en redirigeant les descripteurs de fichiers appropriés et en modifiant +le terminal de contrôle du programme (Voir +.BR tty (4)) +C'est le détail qui fait que +.B reptyr +focntionne bien mieux que les autres programmes du même type, comme +.BR retty (1). + +.LP +Après avoir attaché un programme, il apparaît soit à l'arrière-plan, +soit suspendu pour le shell qui l'a lancé (variable en fonction du +shell). +Pour une sécurité maximale, vous pouvez exécuter +.IP +bg; disown +.LP + +dans le vieux shell pour supprimer l'association avec le programme, +mais +.B reptyr +tente de s'assurer que le programme visé reste en cours d'exécution +même si vous fermez le shell sans le faire. + +.SH OPTIONS + +.B \-l +.IP +Plutôt que d'attacher un nouveau processus, crée un couveau couple de +pty, redirige l'extrémité maîtresse vers le terminal en corus, puis +affiche le nom du pty esclave. Il pourra être passé en argument par +exemple à l'option +.I set inferior-tty +de +.B gdb. +.LP + +.B \-s +.IP + +Par défaut, reptyr déplace tout descripteur de fichier de la cible qui +était connecté au terminal de contrôle vers le nouveau terminal. +L'option +.B -s +fait que reptyr attache les descripteurs de fichiers 0, 1 et 2 sans +condition même si la cible n'a pas de terminal de contrôle ou qu'elle +n'est pas connectée à un terminal. +.LP + +.B \-v +.IP +Affiche la version de +.B reptyr +et sort. +.LP + +.B \-h +.IP +Affiche un message d'usage et sort. +.LP + +.B \-V +.IP +Affiche des messages verbeux. +.LP + +.SH NOTES + +.B reptyr +dépend de l'appel système +.BR ptrace (2) +pour s'attacher au programme distant. Sur Ubuntu Maverick et suivant +cette possibilité est désactivée par défaut pour des raisons de +sécurité. Vous pouvez l'activer temporairement avec +.IP + # echo 0 > /proc/sys/kernel/yama/ptrace_scope +.LP +en tant que rootn ou de manière permanente en éditant le fichier +.IR /etc/sysctl.d/10-ptrace.conf , +ui contient aussi plus d'information sur ce réglage. + +.SH BUGS + +Quand on s'attache à quelques programmes curses, ils ne redessinent +pas immédiatement l'écran, et un +.B ^L +ou équivalent est nécessaire pour forcer l'actualisation. + +De la même manière, après avoir attaché certains programmes, le vieux +terminal est dans un état étrange et un +.B clear +ou même un +.B reset +est nécessaire avant que le vieux terminal ne soit à nouveau +utilisable. + +L'attachement à rtorrent (et peut-être à d'autres applications) ne +fonctionne pas (rtorrent arrête d'accepter des entrées). Le problème +est que rtorrent utilise epoll pour vérifier l'entrée standard et +qu'on ne met pas à jour la référence interne que le descripteur de +fichier d'epoll a de l'ancien terminal. + +L'attachement à un processus avec des fils ne fonctionne pas +correctement. Il devrait être possible de le corriger. Il faut juste +ptracer chaque fils individuellement et de jouer avec lui. + +L'attachement à un processus +.BR less (1) +ne fonctionne pas si vous avez un fichier +.I .lessfilter +parce que +.BR less +abandonne un fils zombie dans ce cas. Ça devrait pouvoir être corrigé. + +Vous pouvez rapporter des bugs à l'auteur (voir ci-dessous) ou par +l'issue tracker sur +GitHub. + +.SH AUTEURS + +reptyr est écrit par Nelson Elhage . + +.SH HOMEPAGE + +.URL https://github.com/nelhage/reptyr + +.SH VOIR AUSSI + +.BR neercs (1), +.BR screen (1) + diff --git a/lib/reptyr/reptyr.h b/lib/reptyr/reptyr.h new file mode 100644 index 0000000..a29e443 --- /dev/null +++ b/lib/reptyr/reptyr.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 by Nelson Elhage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define REPTYR_VERSION "0.4dev" + +int attach_child(pid_t pid, const char *pty, int force_stdio); +#define __printf __attribute__((format(printf, 1, 2))) +void __printf die(const char *msg, ...); +void __printf debug(const char *msg, ...); +void __printf error(const char *msg, ...); diff --git a/lib/reptyr/reptyr.patch b/lib/reptyr/reptyr.patch new file mode 100644 index 0000000..3da3268 --- /dev/null +++ b/lib/reptyr/reptyr.patch @@ -0,0 +1,96 @@ +# -*- coding: utf-8 mode: diff -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +cf https://github.com/nelhage/reptyr + +Ce patch contient les modifications apportées pour transformer la distribution +de reptyr en script pour compileAngGo + +--- ./attach.c.orig 2013-02-02 15:40:12.447986835 +0400 ++++ ./attach.c 2013-02-02 15:42:47.124753835 +0400 +@@ -37,8 +37,8 @@ + #include + #include + +-#include "ptrace.h" +-#include "reptyr.h" ++//#include "ptrace.h" ++//#include "reptyr.h" + + #define TASK_COMM_LENGTH 16 + struct proc_stat { +--- ./arch/i386.h.orig 2013-02-02 15:40:16.596007403 +0400 ++++ ./arch/i386.h 2013-02-02 15:44:10.805168832 +0400 +@@ -19,7 +19,7 @@ + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +-#include "x86_common.h" ++//@include x86_common.h + + static struct ptrace_personality arch_personality[1] = { + { +--- ./arch/amd64.h.orig 2013-02-02 15:40:16.572007302 +0400 ++++ ./arch/amd64.h 2013-02-02 15:44:21.737222992 +0400 +@@ -19,7 +19,7 @@ + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +-#include "x86_common.h" ++//@include x86_common.h + + #define ARCH_HAVE_MULTIPLE_PERSONALITIES + +@@ -58,7 +58,7 @@ + }; + + struct syscall_numbers arch_syscall_numbers[2] = { +-#include "default-syscalls.h" ++//@include default-syscalls.h + { + /* + * These don't seem to be available in any convenient header. We could +--- ./ptrace.c.orig 2013-02-02 15:40:12.455986864 +0400 ++++ ./ptrace.c 2013-02-02 15:45:10.401464274 +0400 +@@ -34,7 +34,7 @@ + #include + #include + +-#include "ptrace.h" ++//#include "ptrace.h" + + /* + * RHEL 5's kernel supports these flags, but their libc doesn't ship a ptrace.h +@@ -79,11 +79,11 @@ + static struct ptrace_personality *personality(struct ptrace_child *child); + + #if defined(__amd64__) +-#include "arch/amd64.h" ++//@include arch/amd64.h + #elif defined(__i386__) +-#include "arch/i386.h" ++//@include arch/i386.h + #elif defined(__arm__) +-#include "arch/arm.h" ++//@include arch/arm.h + #else + #error Unsupported architecture. + #endif +@@ -94,7 +94,7 @@ + } + + struct syscall_numbers arch_syscall_numbers[] = { +-#include "arch/default-syscalls.h" ++//@include arch/default-syscalls.h + }; + #endif + +--- ./reptyr.c.orig 2013-02-02 15:40:12.463986914 +0400 ++++ ./reptyr.c 2013-02-02 15:46:22.157820034 +0400 +@@ -32,7 +32,7 @@ + #include + #include + +-#include "reptyr.h" ++//#include "reptyr.h" + + #ifndef __linux__ + #error reptyr is currently Linux-only. diff --git a/lib/template b/lib/template new file mode 100755 index 0000000..f1bfca9 --- /dev/null +++ b/lib/template @@ -0,0 +1,20 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 +OENC="$UTF8" + +function display_help() { + uecho "$scriptname: + +USAGE + $scriptname [options] + +OPTIONS" +} + +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" diff --git a/lib/templates/auto b/lib/templates/auto new file mode 100755 index 0000000..9006143 --- /dev/null +++ b/lib/templates/auto @@ -0,0 +1,155 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: sélectionner le template approprié pour générer un fichier + +USAGE + $scriptname [template options] + +Si aucun template ne correspond au nom ou à l'extension du fichier, les options +sont analysées à la recherche d'un argument de la forme '-t template' ou +'--template template'. Si un tel argument est trouvé, le template spécifié est +utilisé." +} + +if [ $# -eq 2 ]; then + if [ "$1" == "--matches-template" ]; then + exit 1 + elif [ "$1" == "--matches-name" ]; then + exit 1 + elif [ "$1" == "--matches-ext" ]; then + exit 1 + fi +fi + +#source /etc/ulib && +source "$(dirname "$0")/../../ulib/ulib" && +urequire DEFAULTS || +exit 1 + +[ $# -eq 1 -a "$1" == "--help" ] && exit_with display_help + +function __find_template() { + if [ -z "$template" ]; then + for nt in "${NAMES[@]}"; do + splitpair "$nt" n t + if [ "$name" == "$n" ]; then + template="$t" + break + fi + done + fi + if [ -z "$template" ]; then + for et in "${EXTS[@]}"; do + splitpair "$et" e t + if [ "$ext" == "$e" ]; then + template="$t" + break + fi + done + fi + + found=1 + templ="$templdir/$template" + if [ -z "$template" -o ! -f "$templ" ]; then + found= + if [ -n "$template" ] ; then + for at in "${TEMPLS[@]}"; do + splitpair "$at" a t + if [ "$template" == "$a" ]; then + templ="$templdir/$t" + found=1 + break + fi + [ -n "$found" ] || templ= + done + fi + if [ -z "$found" -a -n "$template" ]; then + for templ in "${templs[@]}"; do + if [ -x "$templ" ] && "$templ" --matches-template "$template"; then + found=1 + break + fi + done + [ -n "$found" ] || templ= + fi + if [ -z "$found" ]; then + for templ in "${templs[@]}"; do + if [ -x "$templ" ] && "$templ" --matches-name "$name"; then + found=1 + break + fi + done + [ -n "$found" ] || templ= + fi + if [ -z "$found" ]; then + for templ in "${templs[@]}"; do + if [ -x "$templ" ] && "$templ" --matches-ext "$ext"; then + found=1 + break + fi + done + [ -n "$found" ] || templ= + fi + fi + + # template trouvé? + [ -n "$found" -a -x "$templ" ] +} + +NAMES=() +EXTS=() +TEMPLATES=() +TEMPLS=() +templdir="$scriptdir" +[ -f "$templdir/templates.conf" ] && source "$templdir/templates.conf" +array_lsfiles templs "$templdir" + +file="$1"; shift +name="$(basename "$file")" +splitlsep "$name" . basename ext +template= + +if ! __find_template; then + # template pas trouvé par rapport au nom du fichier. analyser les arguments + # manuellement à la recherche d'une option -t ou --template + args=() + while [ $# -gt 0 ]; do + case "$1" in + -t|--template) + shift + template="$1" + shift + break + ;; + -t*) + template="${1#-t}" + shift + break + ;; + esac + args=("${args[@]}" "$1") + shift + done + [ -n "$template" ] || die "$file: impossible de trouver le template approprié (essayer avec -t)" + + # restaurer les arguments sans l'option analysée manuellement + set -- "${args[@]}" "$@" + + # corriger éventuellement si un alias de template est utilisé + for at in "${TEMPLATES[@]}"; do + splitpair "$at" a t + if [ "$template" == "$a" ]; then + template="$t" + break + fi + done + + # puis réessayer de trouver le template approprié + __find_template || die "$file: Impossible de trouver le template $template${templ:+ ($(basename "$templ"))}" +fi + +args=() +[ -n "$template" ] && args=("${args[@]}" --template "$template") +exec "$templ" "${args[@]}" "$file" "$@" diff --git a/lib/templates/java b/lib/templates/java new file mode 100755 index 0000000..3b1bd72 --- /dev/null +++ b/lib/templates/java @@ -0,0 +1,285 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: générer un fichier $NAME + +USAGE + $scriptname [options] + +OPTIONS + -t TEMPLATE + Indiquer, le cas échéant, le modèle de fichier à utiliser pour la + génération. Les valeurs valides sont: + ${TEMPLATES[*]} + -e, --edit + -g, --no-edit + Editer (resp. ne pas éditer) le fichier après l'avoir généré. + Par défaut, l'éditeur n'est pas lancé après la génération. + -f, --overwrite + Ecraser le fichier s'il existe déjà + -E, --encoding ENCODING + Spécifier l'encoding à utiliser pour la génération du fichier" +} + +NAME=java +TEMPLATES=(javaproperties java woapi wosrc wosrccomp) +NAMES=(Properties) +EXTS=(properties java api wosrc) + +if [ $# -eq 2 ]; then + if [ "$1" == "--matches-template" ]; then + for template in "${TEMPLATES[@]}"; do + [ "$template" == "$2" ] && exit 0 + done + exit 1 + elif [ "$1" == "--matches-name" ]; then + for name in "${NAMES[@]}"; do + [ "$name" == "$2" ] && exit 0 + done + exit 1 + elif [ "$1" == "--matches-ext" ]; then + for ext in "${EXTS[@]}"; do + [ "$ext" == "$2" ] && exit 0 + done + exit 1 + fi +fi + +#source /etc/ulib && +source "$(dirname "$0")/../../ulib/ulib" && +urequire DEFAULTS || +exit 1 + +function check_overwrite() { + if [ -e "$1" -a -z "$overwrite" ]; then + eerror "$1: refus d'écraser un fichier déjà existant (utiliser -f)" + return 1 + fi + return 0 +} + +function generate_javaproperties() { + local file="$1" + local encoding=iso-8859-1 + local mode= + + check_overwrite "$file" || return + estep "$(ppath "$file")" + echo >"$file" "# -*- coding: $encoding ${mode:+mode: $mode }-*- vim:sw=4:sts=4:et:ai:si:sta:fenc=$encoding" + [ -n "$2" ] && array_add "$2" "$file" + return 0 +} + +function guess_package() { + local dir="$(abspath "$1")" + local -a files + local file + array_lsfiles files "$dir" "*.java" + if [ ${#files[*]} -gt 0 ]; then + # récupérer le package dans un fichier existant + file="${files[0]}" + awk <"$file" ' +/^[ \t]*package[ \t]/ { + if (match($0, /[ \t]*package[ \t]*(.*)[ \t]*;/, vs) != 0) { + print vs[1]; + exit 0 + } +} +# stop quand on arrive à import ou class +/^(import|class)/ { exit 0 }' + elif [[ "$dir" == */Sources/* ]]; then + local package="${dir##*/Sources/}" + package="${package//\//.}" + echo "$package" + fi +} + +function generate_java() { + local file="$1" + local dir="$(dirname "$file")" + local name="$(basename "$file")" + local basename="${name%.*}" + local mode= + + [ -n "$package" ] || local package="$(guess_package "$dir")" + local packline + [ -n "$package" ] && packline="package $package;" + local -a implines + for import in "${imports[@]}"; do + array_add implines "import $import;" + done + implines="$(array_to_lines implines)" + local exts + [ -n "$extends" ] && exts=" extends $extends" + local impls + if [ "${#implements[*]}" -gt 0 ]; then + impls=" implements $(array_join implements ", ")" + fi + + check_overwrite "$file" || return + estep "$(ppath "$file")" + cat >"$file" <"$file" < + + + + +EOF + [ -n "$2" ] && array_add "$2" "$file" + return 0 +} + +function generate_wosrc() { + local file="$1" + + local dtattr + [ -n "$doctype" ] && dtattr=" doctype=\"$doctype\"" + + check_overwrite "$file" || return + estep "$(ppath "$file")" + cat >"$file" < +EOF + if [ -n "$html" ]; then + cat >>"$file" < + head> + title> + body> + h1> +EOF + fi + if [ -n "$blueprint" ]; then + cat >>"$file" < menu="menu" q:title="" +EOF + fi + [ -n "$2" ] && array_add "$2" "$file" + return 0 +} + +function generate_wosrccomp() { + local file="$1" + local dir="$(dirname "$file")" + local name="$(basename "$file")" + local basename="${name%.*}" + local ext="${name##*.}" + case "$ext" in + wosrc|api|java|"") file="$dir/$basename";; + *) die "Destination name should not have an extension" + esac + + etitle "$(ppath "$file")" + generate_wosrc "$file.wosrc"; [ -n "$2" ] && array_add "$2" "$file.wosrc" + generate_woapi "$file.api"; [ -n "$2" ] && array_add "$2" "$file.api" + local -a imports implements + local extends body + imports=(com.webobjects.appserver.WOComponent com.webobjects.appserver.WOContext) + extends=WOComponent + implements=() + body="\ + private static final long serialVersionUID = 1L; + + public ${basename}(WOContext context) { + super(context); + }" + generate_java "$file.java"; [ -n "$2" ] && array_add "$2" "$file.java" + eend + return 0 +} + +template= +edit= +overwrite= +encoding= +package= +imports=() +extends= +implements=() +body= +doctype= +html= +blueprint=1 +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -t:,--template: template= \ + -e,--edit edit=1 \ + -g,--no-edit edit= \ + -f,--overwrite overwrite=1 \ + -E:,--encoding: encoding= \ + --package: package= \ + --imports: imports \ + --extends: extends= \ + --implements: implements \ + --body: body= \ + --doctype: doctype= \ + --html '$html=1; doctype=html4; blueprint=' \ + --blueprint,--bp '$blueprint=1; html=' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +[ -n "$encoding" ] || encoding=utf-8 +array_fix_paths imports +array_fix_paths implements + +files2edit=() +r=0 +for file in "$@"; do + t="$template" + if [ -z "$t" ]; then + # Le cas échéant, si template n'est pas spécifié, le déterminer à partir du + # nom de fichier + case "$(basename "$file")" in + Properties) t=javaproperties;; + esac + fi + + if [ "$t" == javaproperties ]; then + generate_javaproperties "$file" files2edit || r=$? + elif [ "$t" == java ]; then + generate_java "$file" files2edit || r=$? + elif [ "$t" == woapi ]; then + generate_woapi "$file" files2edit || r=$? + elif [ "$t" == wosrc ]; then + generate_wosrc "$file" files2edit || r=$? + elif [ "$t" == wosrccomp ]; then + generate_wosrccomp "$file" files2edit || r=$? + else + die "java: template invalide: $t" + fi +done + +if [ -n "$edit" -a "${#files2edit[*]}" -gt 0 ]; then + "${EDITOR:-vi}" "${files2edit[@]}" +fi + +exit $r diff --git a/lib/templates/script.template b/lib/templates/script.template new file mode 100644 index 0000000..7a5889f --- /dev/null +++ b/lib/templates/script.template @@ -0,0 +1,117 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# copier ce fichier, le rendre exécutable, et remplacer ci-dessous XXX par le nom +# du modèle généré par ce script + +function display_help() { + uecho "$scriptname: générer un fichier $NAME + +USAGE + $scriptname [options] + +OPTIONS + -t TEMPLATE + Indiquer, le cas échéant, le modèle de fichier à utiliser pour la + génération. Les valeurs valides sont: + ${TEMPLATES[*]} + -e, --edit + -g, --no-edit + Editer (resp. ne pas éditer) le fichier après l'avoir généré. + Par défaut, l'éditeur n'est pas lancé après la génération. + -f, --overwrite + Ecraser le fichier s'il existe déjà + -E, --encoding ENCODING + Spécifier l'encoding à utiliser pour la génération du fichier" +} + +NAME=XXX +TEMPLATES=(XXX) +NAMES=() +EXTS=() + +if [ $# -eq 2 ]; then + if [ "$1" == "--matches-template" ]; then + for template in "${TEMPLATES[@]}"; do + [ "$template" == "$2" ] && exit 0 + done + exit 1 + elif [ "$1" == "--matches-name" ]; then + for name in "${NAMES[@]}"; do + [ "$name" == "$2" ] && exit 0 + done + exit 1 + elif [ "$1" == "--matches-ext" ]; then + for ext in "${EXTS[@]}"; do + [ "$ext" == "$2" ] && exit 0 + done + exit 1 + fi +fi + +#source /etc/ulib && +source "$(dirname "$0")/../../ulib/ulib" && +urequire DEFAULTS || +exit 1 + +function check_overwrite() { + if [ -e "$1" -a -z "$overwrite" ]; then + eerror "$1: refus d'écraser un fichier déjà existant (utiliser -f)" + return 1 + fi + return 0 +} + +function generate_XXX() { + local file="$1" + local mode= + + check_overwrite "$1" || return + estep "$(ppath "$file")" + echo >"$file" "# -*- coding: $encoding ${mode:+mode: $mode }-*- vim:sw=4:sts=4:et:ai:si:sta:fenc=$encoding" + [ -n "$2" ] && array_add "$2" "$file" + return 0 +} + +template= +edit= +overwrite= +encoding= +#executable= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -t:,--template: template= \ + -e,--edit edit=1 \ + -g,--no-edit edit= \ + -f,--overwrite overwrite=1 \ + -E:,--encoding: encoding= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + # à rajouter ci-dessus si les fichiers peuvent être exécutables: + #-x,--executable executable=1 \ + #-n,--no-executable executable= \ + +[ -n "$encoding" ] || encoding=utf-8 + +files2edit=() +r=0 +for file in "$@"; do + t="$template" + if [ -z "$t" ]; then + # Le cas échéant, si template n'est pas spécifié, le déterminer à partir du + # nom de fichier + dir="$(dirname "$file")" + name="$(basename "$file")" + fi + + if [ "$t" == XXX ]; then + generate_XXX "$file" files2edit || r=$? + else + die "XXX: template invalide: $t" + fi +done + +if [ -n "$edit" -a "${#files2edit[*]}" -gt 0 ]; then + "${EDITOR:-vi}" "${files2edit[@]}" +fi + +exit $r diff --git a/lib/templates/shell b/lib/templates/shell new file mode 100755 index 0000000..bb136a0 --- /dev/null +++ b/lib/templates/shell @@ -0,0 +1,205 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: générer un fichier $NAME + +USAGE + $scriptname [options] + +OPTIONS + -t TEMPLATE + Indiquer, le cas échéant, le modèle de fichier à utiliser pour la + génération. Les valeurs valides sont: + ${TEMPLATES[*]} + -e, --edit + -g, --no-edit + Editer (resp. ne pas éditer) le fichier après l'avoir généré. + Par défaut, l'éditeur est lancé après la génération. + -f, --overwrite + Ecraser le fichier s'il existe déjà + -E, --encoding ENCODING + Spécifier l'encoding à utiliser pour la génération du fichier" +} + +NAME=shell +TEMPLATES=(shell) +NAMES=() +EXTS=(sh) + +if [ $# -eq 2 ]; then + if [ "$1" == "--matches-template" ]; then + for template in "${TEMPLATES[@]}"; do + [ "$template" == "$2" ] && exit 0 + done + exit 1 + elif [ "$1" == "--matches-name" ]; then + for name in "${NAMES[@]}"; do + [ "$name" == "$2" ] && exit 0 + done + exit 1 + elif [ "$1" == "--matches-ext" ]; then + for ext in "${EXTS[@]}"; do + [ "$ext" == "$2" ] && exit 0 + done + exit 1 + fi +fi + +#source /etc/ulib && +source "$(dirname "$0")/../../ulib/ulib" && +urequire DEFAULTS || +exit 1 + +function check_overwrite() { + if [ -e "$1" -a -z "$overwrite" ]; then + eerror "$1: refus d'écraser un fichier déjà existant (utiliser -f)" + return 1 + fi + return 0 +} + +function generate_shell() { + local file="$1" + local dir="$(dirname "$file")" + local mode=sh + + check_overwrite "$1" || return + + local modeline="# -*- coding: $encoding ${mode:+mode: $mode }-*- vim:sw=4:sts=4:et:ai:si:sta:fenc=$encoding" + if [ -n "$executable" ]; then + etitle "$(ppath "$file")" + + local type= + if [ -d "$dir/ulib" ]; then + ask_yesno "\ +Le type de script 'system-or-local' a été sélectionné automatiquement +Une librairie ulib locale est présente. En chargeant ulib/auto, la librairie +système /etc/ulib sera utilisée si elle est disponible. Sinon, la librairie +locale ulib/ulib sera utilisée. +Voulez-vous générer un script avec cette configuration?" X && + type=system-or-local + elif [ -f /etc/ulibauto ]; then + ask_yesno "\ +Le type de script 'default' a été sélectionné automatiquement +En chargeant /etc/ulibauto, la librairie ulib système sera utilisée, et les +paramètres par défaut seront utilisés. +Voulez-vous générer un script avec cette configuration?" X && + type=default + else + ask_yesno "\ +Le type de script 'manual' a été sélectionné automatiquement +Une vieille version de nutools est installée. Il faut charger /etc/ulib et +définir manuellement les paramètres à utiliser. +Voulez-vous générer un script avec cette configuration?" X && + type=manual + fi + if [ -z "$type" ]; then + enote "Plusieurs type de scripts peuvent être générés: +- Avec le type 'system-or-local', on assume qu'une librairie ulib locale est + présente. Le fichier ulib/auto est chargé, ce qui a pour conséquence que la + librairie système /etc/ulib est utilisée si elle est disponible. Sinon, la + librairie locale ulib/ulib est utilisée. +- Avec le type 'default', le fichier /etc/ulibauto est chargé. Les paramètres + par défaut sont utilisés: urequire DEFAULTS et genparse() pour l'analyse des + arguments. +- Avec le type 'manual', le fichier /etc/ulib est chargé. Les paramètres par + défaut sont utilisés manuellement: urequire DEFAULTS et parse_opts() pour + l'analyse des arguments. +- Avec le type 'vanilla', un script simple est généré." + local -a types + types=(system-or-local default manual vanilla) + simple_menu type types \ + -t "Choix du type de script" \ + -m "Veuillez choisir le type de script à générer" \ + -d manual + fi + + if [ "$type" == system-or-local ]; then + echo >"$file" '#!/bin/bash +'"$modeline"' +source "$(dirname "$0")/ulib/auto" +eval "$(genparse)" +' + elif [ "$type" == default ]; then + echo >"$file" '#!/bin/bash +'"$modeline"' +source /etc/ulibauto +eval "$(genparse)" +' + elif [ "$type" == manual ]; then + echo >"$file" '#!/bin/bash +'"$modeline"' + +function display_help() { + uecho "$scriptname: + +USAGE + $scriptname [options] + +OPTIONS" +} + +source /etc/ulib && +urequire DEFAULTS || +exit 1 + +parse_opts "${PRETTYOPTS[@]}" \ + --help '\''$exit_with display_help'\'' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" +' + elif [ "$type" == vanilla ]; then + echo >"$file" "#!/bin/bash +$modeline" + fi + chmod +x "$file" + eend + else + estep "$(ppath "$file")" + echo >"$file" "$modeline" + fi + [ -n "$2" ] && array_add "$2" "$file" + return 0 +} + +template= +edit=1 +overwrite= +encoding= +executable=1 +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -t:,--template: template= \ + -e,--edit edit=1 \ + -g,--no-edit edit= \ + -f,--overwrite overwrite=1 \ + -E:,--encoding: encoding= \ + -x,--executable executable=1 \ + -n,--no-executable executable= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +[ -n "$encoding" ] || encoding=utf-8 + +files2edit=() +r=0 +for file in "$@"; do + t="$template" + if [ -z "$t" ]; then + # Le cas échéant, si template n'est pas spécifié, le déterminer à partir du + # nom de fichier + dir="$(dirname "$file")" + name="$(basename "$file")" + fi + + if [ "$t" == shell ]; then + generate_shell "$file" files2edit || r=$? + else + die "shell: template invalide: $t" + fi +done + +if [ -n "$edit" -a "${#files2edit[*]}" -gt 0 ]; then + "${EDITOR:-vi}" "${files2edit[@]}" +fi + +exit $r diff --git a/lib/templates/templates.conf b/lib/templates/templates.conf new file mode 100644 index 0000000..30f5528 --- /dev/null +++ b/lib/templates/templates.conf @@ -0,0 +1,47 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Noms et extensions reconnus +NAMES=( + Properties:javaproperties +) +EXTS=( + txt:text + conf:conf + sql:sql + mdt:markdown mdwn:markdown md:markdown text:markdown + twp:twpage + puml:puml + iuml:iuml + sh:shell + py:pyfile pyw:pyfile + css:css + js:javascript + html:html4 + php:php phps:php + ctp:ctp + java:java api:wosrccomp wosrc:wosrccomp properties:javaproperties +) + +# Aliases de templates +TEMPLATES=( + t:text c:conf + md:markdown twp:twpage + plantuml:puml + sh:shell + python:pyfile pyf:pyfile py:pyfile + pym:pymodule + js:javascript + html:html4 + properties:javaproperties jprops:javaproperties props:javaproperties +) + +# Scripts à utiliser pour générer les templates +TEMPLS=( + text:text conf:text sql:text + markdown:wiki twpage:wiki + puml:plantuml iuml:plantuml + shell:shell + pyfile:python pymodule:python + css:www javascript:www html4:www php:www ctp:www + javaproperties:java java:java woapi:java wosrc:java wosrccomp:java +) diff --git a/lib/tiddlywiki/TiddlySaver.jar b/lib/tiddlywiki/TiddlySaver.jar new file mode 100644 index 0000000000000000000000000000000000000000..e42d06d3a54b21c5c925b95b239803bf604ace66 GIT binary patch literal 4982 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 UQE#>3?MB`{ZMPd*@E7KP065KJq5uE@ literal 0 HcmV?d00001 diff --git a/lib/tiddlywiki/download-latest.sh b/lib/tiddlywiki/download-latest.sh new file mode 100755 index 0000000..613259b --- /dev/null +++ b/lib/tiddlywiki/download-latest.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$(dirname "$0")/../../ulib/ulib" && +urequire DEFAULTS tiddlywiki || +exit 1 +OENC="$UTF8" + +function display_help() { + echo "$scriptname: Télécharger la dernière version de TiddlyWiki" +} + +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +cd "$scriptdir" + +tmpfile="empty.html.$$" +autoclean "$tmpfile" + +estep "Téléchargement de la dernière version" +wget -q -O "$tmpfile" http://www.tiddlywiki.com/empty.html || die + +if testdiff "$tmpfile" empty.html; then + version="$(get_version "$tmpfile")" + estep "Une nouvelle version est disponible ($version)" + read_value -i "Confirmez le numéro de version" version "$version" + + versionfile="empty-$version.html" + /bin/mv "$tmpfile" "$versionfile" + /bin/cp -f "$versionfile" empty.html +else + estep "La dernière version $(get_version empty.html) est déjà disponible en local" +fi diff --git a/lib/tiddlywiki/empty.html b/lib/tiddlywiki/empty.html new file mode 100644 index 0000000..53c776c --- /dev/null +++ b/lib/tiddlywiki/empty.html @@ -0,0 +1,10147 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
<!--{{{-->
+<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
+<!--}}}-->
+
+
+
+
Background: #fff
+Foreground: #000
+PrimaryPale: #8cf
+PrimaryLight: #18f
+PrimaryMid: #04b
+PrimaryDark: #014
+SecondaryPale: #ffc
+SecondaryLight: #fe8
+SecondaryMid: #db4
+SecondaryDark: #841
+TertiaryPale: #eee
+TertiaryLight: #ccc
+TertiaryMid: #999
+TertiaryDark: #666
+Error: #f88
+
+
+
+
/*{{{*/
+body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
+
+a {color:[[ColorPalette::PrimaryMid]];}
+a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
+a img {border:0;}
+
+h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
+h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
+h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}
+
+.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
+.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
+.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}
+
+.header {background:[[ColorPalette::PrimaryMid]];}
+.headerShadow {color:[[ColorPalette::Foreground]];}
+.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
+.headerForeground {color:[[ColorPalette::Background]];}
+.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}
+
+.tabSelected {color:[[ColorPalette::PrimaryDark]];
+	background:[[ColorPalette::TertiaryPale]];
+	border-left:1px solid [[ColorPalette::TertiaryLight]];
+	border-top:1px solid [[ColorPalette::TertiaryLight]];
+	border-right:1px solid [[ColorPalette::TertiaryLight]];
+}
+.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
+.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
+.tabContents .button {border:0;}
+
+#sidebar {}
+#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
+#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
+#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
+#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
+#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}
+
+.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
+.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
+.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
+.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
+	border:1px solid [[ColorPalette::PrimaryMid]];}
+.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
+.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
+.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
+.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
+	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
+.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
+.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
+	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}
+
+.wizard .notChanged {background:transparent;}
+.wizard .changedLocally {background:#80ff80;}
+.wizard .changedServer {background:#8080ff;}
+.wizard .changedBoth {background:#ff8080;}
+.wizard .notFound {background:#ffff80;}
+.wizard .putToServer {background:#ff80ff;}
+.wizard .gotFromServer {background:#80ffff;}
+
+#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
+#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}
+
+.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}
+
+.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
+.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
+.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
+.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
+.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
+.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
+.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
+.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}
+
+.tiddler .defaultCommand {font-weight:bold;}
+
+.shadow .title {color:[[ColorPalette::TertiaryDark]];}
+
+.title {color:[[ColorPalette::SecondaryDark]];}
+.subtitle {color:[[ColorPalette::TertiaryDark]];}
+
+.toolbar {color:[[ColorPalette::PrimaryMid]];}
+.toolbar a {color:[[ColorPalette::TertiaryLight]];}
+.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
+.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}
+
+.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
+.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
+.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
+.tagging .button, .tagged .button {border:none;}
+
+.footer {color:[[ColorPalette::TertiaryLight]];}
+.selected .footer {color:[[ColorPalette::TertiaryMid]];}
+
+.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
+.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
+.lowlight {background:[[ColorPalette::TertiaryLight]];}
+
+.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}
+
+.imageLink, #displayArea .imageLink {background:transparent;}
+
+.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}
+
+.viewer .listTitle {list-style-type:none; margin-left:-2em;}
+.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
+.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}
+
+.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
+.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
+.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}
+
+.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
+.viewer code {color:[[ColorPalette::SecondaryDark]];}
+.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
+
+.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}
+
+.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
+.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
+.editorFooter {color:[[ColorPalette::TertiaryMid]];}
+.readOnly {background:[[ColorPalette::TertiaryPale]];}
+
+#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
+#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
+#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
+#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
+#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
+#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
+#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
+.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
+.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
+#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:alpha(opacity=60);}
+/*}}}*/
+
+
+
/*{{{*/
+* html .tiddler {height:1%;}
+
+body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}
+
+h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
+h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
+h4,h5,h6 {margin-top:1em;}
+h1 {font-size:1.35em;}
+h2 {font-size:1.25em;}
+h3 {font-size:1.1em;}
+h4 {font-size:1em;}
+h5 {font-size:.9em;}
+
+hr {height:1px;}
+
+a {text-decoration:none;}
+
+dt {font-weight:bold;}
+
+ol {list-style-type:decimal;}
+ol ol {list-style-type:lower-alpha;}
+ol ol ol {list-style-type:lower-roman;}
+ol ol ol ol {list-style-type:decimal;}
+ol ol ol ol ol {list-style-type:lower-alpha;}
+ol ol ol ol ol ol {list-style-type:lower-roman;}
+ol ol ol ol ol ol ol {list-style-type:decimal;}
+
+.txtOptionInput {width:11em;}
+
+#contentWrapper .chkOptionInput {border:0;}
+
+.externalLink {text-decoration:underline;}
+
+.indent {margin-left:3em;}
+.outdent {margin-left:3em; text-indent:-3em;}
+code.escaped {white-space:nowrap;}
+
+.tiddlyLinkExisting {font-weight:bold;}
+.tiddlyLinkNonExisting {font-style:italic;}
+
+/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
+a.tiddlyLinkNonExisting.shadow {font-weight:bold;}
+
+#mainMenu .tiddlyLinkExisting,
+	#mainMenu .tiddlyLinkNonExisting,
+	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
+#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}
+
+.header {position:relative;}
+.header a:hover {background:transparent;}
+.headerShadow {position:relative; padding:4.5em 0 1em 1em; left:-1px; top:-1px;}
+.headerForeground {position:absolute; padding:4.5em 0 1em 1em; left:0; top:0;}
+
+.siteTitle {font-size:3em;}
+.siteSubtitle {font-size:1.2em;}
+
+#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}
+
+#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
+#sidebarOptions {padding-top:0.3em;}
+#sidebarOptions a {margin:0 0.2em; padding:0.2em 0.3em; display:block;}
+#sidebarOptions input {margin:0.4em 0.5em;}
+#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
+#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
+#sidebarOptions .sliderPanel input {margin:0 0 0.3em 0;}
+#sidebarTabs .tabContents {width:15em; overflow:hidden;}
+
+.wizard {padding:0.1em 1em 0 2em;}
+.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em;}
+.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0; margin:0.4em 0 0.2em;}
+.wizardStep {padding:1em 1em 1em 1em;}
+.wizard .button {margin:0.5em 0 0; font-size:1.2em;}
+.wizardFooter {padding:0.8em 0.4em 0.8em 0;}
+.wizardFooter .status {padding:0 0.4em; margin-left:1em;}
+.wizard .button {padding:0.1em 0.2em;}
+
+#messageArea {position:fixed; top:2em; right:0; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
+.messageToolbar {display:block; text-align:right; padding:0.2em;}
+#messageArea a {text-decoration:underline;}
+
+.tiddlerPopupButton {padding:0.2em;}
+.popupTiddler {position: absolute; z-index:300; padding:1em; margin:0;}
+
+.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
+.popup .popupMessage {padding:0.4em;}
+.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0;}
+.popup li.disabled {padding:0.4em;}
+.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
+.listBreak {font-size:1px; line-height:1px;}
+.listBreak div {margin:2px 0;}
+
+.tabset {padding:1em 0 0 0.5em;}
+.tab {margin:0 0 0 0.25em; padding:2px;}
+.tabContents {padding:0.5em;}
+.tabContents ul, .tabContents ol {margin:0; padding:0;}
+.txtMainTab .tabContents li {list-style:none;}
+.tabContents li.listLink { margin-left:.75em;}
+
+#contentWrapper {display:block;}
+#splashScreen {display:none;}
+
+#displayArea {margin:1em 17em 0 14em;}
+
+.toolbar {text-align:right; font-size:.9em;}
+
+.tiddler {padding:1em 1em 0;}
+
+.missing .viewer,.missing .title {font-style:italic;}
+
+.title {font-size:1.6em; font-weight:bold;}
+
+.missing .subtitle {display:none;}
+.subtitle {font-size:1.1em;}
+
+.tiddler .button {padding:0.2em 0.4em;}
+
+.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
+.isTag .tagging {display:block;}
+.tagged {margin:0.5em; float:right;}
+.tagging, .tagged {font-size:0.9em; padding:0.25em;}
+.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
+.tagClear {clear:both;}
+
+.footer {font-size:.9em;}
+.footer li {display:inline;}
+
+.annotation {padding:0.5em; margin:0.5em;}
+
+* html .viewer pre {width:99%; padding:0 0 1em 0;}
+.viewer {line-height:1.4em; padding-top:0.5em;}
+.viewer .button {margin:0 0.25em; padding:0 0.25em;}
+.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
+.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}
+
+.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
+.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
+table.listView {font-size:0.85em; margin:0.8em 1.0em;}
+table.listView th, table.listView td, table.listView tr {padding:0 3px 0 3px;}
+
+.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
+.viewer code {font-size:1.2em; line-height:1.4em;}
+
+.editor {font-size:1.1em;}
+.editor input, .editor textarea {display:block; width:100%; font:inherit;}
+.editorFooter {padding:0.25em 0; font-size:.9em;}
+.editorFooter .button {padding-top:0; padding-bottom:0;}
+
+.fieldsetFix {border:0; padding:0; margin:1px 0px;}
+
+.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
+.zoomer div {padding:1em;}
+
+* html #backstage {width:99%;}
+* html #backstageArea {width:99%;}
+#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em;}
+#backstageToolbar {position:relative;}
+#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em;}
+#backstageButton {display:none; position:absolute; z-index:175; top:0; right:0;}
+#backstageButton a {padding:0.1em 0.4em; margin:0.1em;}
+#backstage {position:relative; width:100%; z-index:50;}
+#backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin-left:3em; padding:1em;}
+.backstagePanelFooter {padding-top:0.2em; float:right;}
+.backstagePanelFooter a {padding:0.2em 0.4em;}
+#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}
+
+.whenBackstage {display:none;}
+.backstageVisible .whenBackstage {display:block;}
+/*}}}*/
+
+
+
+
/***
+StyleSheet for use when a translation requires any css style changes.
+This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
+***/
+/*{{{*/
+body {font-size:0.8em;}
+#sidebarOptions {font-size:1.05em;}
+#sidebarOptions a {font-style:normal;}
+#sidebarOptions .sliderPanel {font-size:0.95em;}
+.subtitle {font-size:0.8em;}
+.viewer table.listView {font-size:0.95em;}
+/*}}}*/
+
+
+
/*{{{*/
+@media print {
+#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none !important;}
+#displayArea {margin: 1em 1em 0em;}
+noscript {display:none;} /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
+}
+/*}}}*/
+
+
+
<!--{{{-->
+<div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
+<div class='headerShadow'>
+<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
+<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
+</div>
+<div class='headerForeground'>
+<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
+<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
+</div>
+</div>
+<div id='mainMenu' refresh='content' tiddler='MainMenu'></div>
+<div id='sidebar'>
+<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
+<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
+</div>
+<div id='displayArea'>
+<div id='messageArea'></div>
+<div id='tiddlerDisplay'></div>
+</div>
+<!--}}}-->
+
+
+
<!--{{{-->
+<div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
+<div class='title' macro='view title'></div>
+<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
+<div class='tagging' macro='tagging'></div>
+<div class='tagged' macro='tags'></div>
+<div class='viewer' macro='view text wikified'></div>
+<div class='tagClear'></div>
+<!--}}}-->
+
+
+
<!--{{{-->
+<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
+<div class='title' macro='view title'></div>
+<div class='editor' macro='edit title'></div>
+<div macro='annotations'></div>
+<div class='editor' macro='edit text'></div>
+<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser excludeLists'></span></div>
+<!--}}}-->
+
+
+
To get started with this blank [[TiddlyWiki]], you'll need to modify the following tiddlers:
+* [[SiteTitle]] & [[SiteSubtitle]]: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
+* [[MainMenu]]: The menu (usually on the left)
+* [[DefaultTiddlers]]: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
+You'll also need to enter your username for signing your edits: <<option txtUserName>>
+
+
+
These [[InterfaceOptions]] for customising [[TiddlyWiki]] are saved in your browser
+
+Your username for signing your edits. Write it as a [[WikiWord]] (eg [[JoeBloggs]])
+
+<<option txtUserName>>
+<<option chkSaveBackups>> [[SaveBackups]]
+<<option chkAutoSave>> [[AutoSave]]
+<<option chkRegExpSearch>> [[RegExpSearch]]
+<<option chkCaseSensitiveSearch>> [[CaseSensitiveSearch]]
+<<option chkAnimate>> [[EnableAnimations]]
+
+----
+Also see [[AdvancedOptions]]
+
+
+
<<importTiddlers>>
+
+
+ +
+
+ + + + + + + + + + + + diff --git a/lib/tiddlywiki/tiddlywiki.url b/lib/tiddlywiki/tiddlywiki.url new file mode 100644 index 0000000..6256a17 --- /dev/null +++ b/lib/tiddlywiki/tiddlywiki.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://tiddlywiki.com/ diff --git a/lib/uinst/conf b/lib/uinst/conf new file mode 100644 index 0000000..9e4ad35 --- /dev/null +++ b/lib/uinst/conf @@ -0,0 +1,48 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$@" || exit 1 +source "$ULIBDIR/ulib" && urequire DEFAULTS uenv uenv_update || exit 1 +cd "$scriptdir/../.." + +# supprimer fichiers de développement +rm -rf pyulib/{build,devel,migrate,test} + +# liens pour les scripts python +for i in plver plbck uencdetect urandomize; do + ln -s lib/pywrapper "$i" +done + +# liens pour les scripts shell +for i in cg cgs; do + ln -s compileAndGo "$i" +done +./fnconv --nutools-makelinks +./fconv --nutools-makelinks +./uproject --nutools-makelinks +./woctl --nutools-makelinks +./dokuwiki --nutools-makelinks +./mediawiki --nutools-makelinks +./uawk --nutools-makelinks +./udist --nutools-makelinks + +# copier le fichier .nutoolsrc +[ -f ~/.nutoolsrc ] || cp lib/nutoolsrc ~/.nutoolsrc + +# identification du système +echo "##@before *" >lib/profile.d/0nutools +set_var_cmd UNAME_SYSTEM "$UNAME_SYSTEM" >>lib/profile.d/0nutools +set_var_cmd UNAME_MACHINE "$UNAME_MACHINE" >>lib/profile.d/0nutools + +# installer les profils +destdir="@@dest@@" +is_yes "$install_profiles" && uenv_configure_profiles "$destdir" + +if [ -d "profile.d" ]; then + estep "Mise à jour de l'ordre de lecture de $(ppath "$destdir/profile.d")" + uenv_update_dir profile.d "" "$destdir/profile.d" +fi + +if [ -d "bashrc.d" ]; then + estep "Mise à jour de l'ordre de lecture de $(ppath "$destdir/bashrc.d")" + uenv_update_dir bashrc.d "" "$destdir/bashrc.d" +fi diff --git a/lib/uinst/rootconf b/lib/uinst/rootconf new file mode 100644 index 0000000..354bd7c --- /dev/null +++ b/lib/uinst/rootconf @@ -0,0 +1,72 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$@" || exit 1 +source "$ULIBDIR/ulib" && urequire DEFAULTS || exit 1 + +cd "$scriptdir/../../" +log=/tmp/nutools-pyulib-install.log +etitle -s "Installation des packages python" +enote "Le log de l'installation se trouve dans $log" +./uinst -y pyulib >&"$log" +eend + +# setup.py laisse des fichiers avec les droits de root, et ces fichiers ne +# peuvent être supprimés par l'utilisateur qui lance uinst. Les supprimer ici. +rm -rf pyulib/build + +dest="@@dest@@" +for i in ulib ulibsh; do + sed "s|@@""dest""@@|$dest|g" "ulib/$i" >"/etc/$i" +done +echo >/etc/ulibauto '# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +if [ x"$BASH" != x -a -f /etc/ulib ]; then + . /etc/ulib +elif [ x"$BASH" = x -a -f /etc/ulibsh ]; then + . /etc/ulibsh +else + echo "error: Unable to find required file /etc/ulib" 1>&2 + exit 1 +fi +uprovide ulibauto +urequire DEFAULTS' + +if [ -n "$uninst_utools" ]; then + etitle "Désinstallation de la configuration de utools" + array_from_lines userinfos "$(/dev/null || return + eval "/bin/ls -1d ${*:-*} 2>/dev/null" + cd "$curdir" +} +# lister les fichiers du répertoire $1, un par ligne +function list_files() { + # $1=un répertoire dont le contenu doit être listé. + # $2..@=un ensemble de patterns pour le listage + local f + local curdir="$(pwd)" + local b="${1:-.}"; shift + + cd "$b" 2>/dev/null || return + eval "/bin/ls -1d ${*:-*} 2>/dev/null" | while read f; do + [ -f "$f" ] && echo "$f" + done + cd "$curdir" +} +# lister les répertoires du répertoire $1, un par ligne +function list_dirs() { + # $1=un répertoire dont le contenu doit être listé. + # $2..@=un ensemble de patterns pour le listage + local f + local curdir="$(pwd)" + local b="${1:-.}"; shift + + cd "$b" 2>/dev/null || return + eval "/bin/ls -1d ${*:-*} 2>/dev/null" | while read f; do + [ -d "$f" ] && echo "$f" + done + cd "$curdir" +} +# copier un fichier dans un répertoire, ou le contenu d'un répertoire dans un +# autre répertoire, que le répertoire source soit un lien symbolique ou +# non. Cette fonction existe parce que le comportement de "cp_a src dest" n'est +# pas consistant selon les plateformes, surtout si src est un lien symbolique +# sur un répertoire: parfois on copie le lien, parfois on copie le contenu du +# répertoire, parfois on copie le répertoire... +function cpdir() { + # $1=src, $2=dest, $3=method (cp_a par défaut) + local src="$1" dest="$2" method="${3:-cp_a}" + + if [ -d "$src" ]; then + # si c'est un répertoire, traitement particulier + # tout d'abord, s'assurer que la destination existe + if [ ! -d "$dest" ]; then + mkdir -p "$dest" + fi + + # ensuite on fait la copie + local prevdir="$(pwd)" + + dest="$(abspath "$dest")" # XXX abspath défini dans functions! + cd "$src" + if [ -n "$(/bin/ls -a1)" ]; then + # copier les fichiers + [ -n "$(/bin/ls -1)" ] && "$method" * "$dest" + # ne pas oublier les fichiers cachés... + local i + for i in .*; do + [ "$i" == "." -o "$i" == ".." ] && continue + "$method" "$i" "$dest" + done + fi + cd "$prevdir" + else + # sinon, on assume que c'est un fichier + if [ -f "$dest" ]; then + # copie du fichier avec remplacement + "$method" "$src" "$dest" + elif [ -d "$dest" ]; then + # copie du fichier dans le répertoire + "$method" "$src" "$dest" + else + # Copie du fichier avec le nom $dest + mkdirof "$dest" + "$method" "$src" "$dest" + fi + fi +} +# transformer les fins de ligne en LF +function nl2lf() { + if [ -n "$1" -a "$1" != "-" ]; then + tr -d '\r' <"$1" >"$1.tmp.$$" && + /bin/mv "$1.tmp.$$" "$1" + [ -f "$1.tmp.$$" ] && /bin/rm -f "$1.tmp.$$" + else + tr -d '\r' + fi +} +# transformer les fins de ligne en CRLF +function _nl2crlf() { + tr -d '\r' | awk '{print $0 "\r"}' +} +function nl2crlf() { + if [ -n "$1" -a "$1" != "-" ]; then + _nl2crlf <"$1" >"$1.tmp.$$" && + /bin/mv "$1.tmp.$$" "$1" + [ -f "$1.tmp.$$" ] && /bin/rm -f "$1.tmp.$$" + else + _nl2crlf + fi +} +# tester la présence d'un pattern dans un fichier +function quietgrep() { grep -q "$@"; } +# tester si deux fichiers sont identiques +function quietdiff() { diff -q "$@" >&/dev/null; } +# tester si deux fichiers sont identiques/différents +function testsame() { quietdiff "$@"; } +function testdiff() { ! quietdiff "$@"; } +# test si $2 n'existe pas ou si $1 est plus récent que $2 +function testnewer() { test ! -e "$2" -o "$1" -nt "$2"; } +# afficher tous les processus avec le maximum d'informations +function ps_all() { ps -axww; } +# tester l'existence d'un programme dans le PATH +function progexists() { test -n "$1" -a -x "$(which "$1" 2>/dev/null)"; } +# tester si on est root +function is_root() { test `id -u` -eq 0; } +# sourcer un fichier s'il existe +function source_ifexists() { if [ -f "$1" ]; then source "$1" || die; fi; } +# s'endormir pour une très petite période de temps +function little_sleep { LC_NUMERIC=C sleep 0.1; } +# tester si un programme dont on donne le PID tourne +function is_running() { test -n "$(ps -o pid -p "$1" | grep -v PID)"; } +# afficher des chemins "jolis" +function ppath() { + # Dans un chemin *absolu*, remplacer "$HOME" par "~" et "$(pwd)/" par "", + # afin que le chemin soit plus facile à lire. Le répertoire courant est + # spécifié par $2 ou $(pwd) si $2 est vide + local path="$1" cwd="$2" + + # essayer de normaliser le chemin + if [ -d "$path" ]; then path="$(cd "$path"; pwd)" + elif [ -f "$path" ]; then path="$(cd "$(dirname "$path")"; pwd)/$(basename "$path")" + else + : # chemin inexistant, on espère que le chemin est bien formé + fi + + if [ -z "$2" ]; then + cwd="$(pwd)" + fi + [ "$path" = "$cwd" ] && path="." + [ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path/#$cwd\//}" + path="${path/#$HOME/~}" + + echo "$path" +} +# un équivalent de basename écris en bash. note: le cas $1=/ n'est pas traité correctement +function bname() { + local bn="${1%%/}" + bn="${p##*/}" + [ -n "$2" ] && bn="${p%%$2}" + echo "$bn" +} +# un équivalent de dirname écris en bash +function dname() { + local dn="${1%%/}" + case "$dn" in + */*) + dn="${dn%/*}" + ;; + *) + dn="." + ;; + esac + echo "$dn" +} +# sed qui modifie le fichier en place. pour compatibilité avec d'autres +# plateformes (cf system_caps), cette version ne devrait être appelée que de +# cette manière:: +# sedi "script" [files...] +# toute autre utilisation fonctionnerait sur Linux mais pas sur MacOS X ou SunOS +function sedi() { + sed -i "$@" +} +# run_as: fonction pour relancer le script courant afin qu'il tourne avec les +# droits d'un autre user (typiquement root). Attention! cette fonction ne teste +# pas avec si on est déjà ce user. Il y a donc un risque de boucle infinie si on +# ne teste pas le user courant. +function run_as() { + # Lancer cette fonction avec les arguments du script en cours. Par exemple:: + # run_as root "$@" + # Si $2=--noexec, on n'utilise pas la fonction exec, ce qui fait que la + # fonction retourne. Sinon, on peut considérer que cette fonction ne + # retourne jamais + local OENC="$UTF8" + local user="${1:-root}"; shift + local exec_maybe=exec + if [ "$1" = "--noexec" ]; then + exec_maybe= + shift + fi + + # on peut spécifer localement le niveau de verbosité + local VERBOSITY="$VERBOSITY" INTERACTION="$INTERACTION" + set_verbosity "$1" && shift + + estep "Lancement du script sur $HOSTNAME en tant que $user" + if is_yes "$UTOOLS_USES_SU" || ! progexists sudo; then + einfo "Entrez le mot de passe de root" + $exec_maybe su "$user" -c "${BASH:-/bin/sh} \"$0\" $*" + else + einfo "Entrez le mot de passe de $USER (si nécessaire)" + if [ "$user" == "root" ]; then + $exec_maybe sudo "${BASH:-/bin/sh}" "$0" "$@" + else + local args="$*" + args="${args//|/\\|}" + args="${args///\\>}" + args="${args//;/\\;}" + $exec_maybe sudo su "$user" -c "${BASH:-/bin/sh} \"$0\" $args" + fi + fi +} +# run_as_root: fonction pour relancer le script courant afin qu'il tourne en +# root. Attention! cette fonction ne teste pas avec is_root si on est déjà en +# root ou non. Ne pas appeler cette fonction si is_root est vrai, on risque une +# boucle infinie. +function run_as_root() { + run_as root "$@" +} + +# === fonctions pour contrôler l'encoding en sortie et en entrée === +LATIN1=iso-8859-1 +LATIN9=iso-8859-15 +UTF8=utf-8 + +if ! progexists iconv; then + function iconv() { cat; } +fi + +function __lang_encoding() { + local lang="$(<<<"$LANG" awk '{ print tolower($0) }')" + case "$lang" in + fr_fr@euro) echo "iso-8859-15";; + fr_fr) echo "iso-8859-1";; + *.utf-8) echo "utf-8";; + *) ;; + esac +} +function __norm_encoding() { + awk '{ + enc = tolower($0) + gsub(/^latin$/, "latin1", enc) + gsub(/^latin1$/, "iso-8859-1", enc) + gsub(/^latin9$/, "iso-8859-15", enc) + gsub(/[-_]/, "", enc) + if (enc == "iso8859" || enc == "iso88591" || enc == "8859" || enc == "88591") print "iso-8859-1" + else if (enc == "iso885915" || enc == "885915") print "iso-8859-15" + else if (enc == "utf8") print "utf-8" + else print $0 + }' <<<"$1" +} +function __init_encoding() { + local DEFAULT_ENCODING="$(__lang_encoding)" + [ -n "$DEFAULT_ENCODING" ] || DEFAULT_ENCODING=utf-8 + [ -n "$UTOOLS_OUTPUT_ENCODING" ] || UTOOLS_OUTPUT_ENCODING="$DEFAULT_ENCODING" + UTOOLS_OUTPUT_ENCODING="$(__norm_encoding "$UTOOLS_OUTPUT_ENCODING")" + [ -n "$UTOOLS_INPUT_ENCODING" ] || UTOOLS_INPUT_ENCODING="$UTOOLS_OUTPUT_ENCODING" + UTOOLS_INPUT_ENCODING="$(__norm_encoding "$UTOOLS_INPUT_ENCODING")" + [ -n "$UTOOLS_EDITOR_ENCODING" ] || UTOOLS_EDITOR_ENCODING="$UTOOLS_INPUT_ENCODING" + UTOOLS_EDITOR_ENCODING="$(__norm_encoding "$UTOOLS_EDITOR_ENCODING")" + + IENC="$UTOOLS_INPUT_ENCODING" + OENC="$UTOOLS_OUTPUT_ENCODING" +} +__init_encoding + +function tooenc() { + local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + echo "$src" + else + iconv -f "$from" -t "$to" <<<"$src" + fi +} +function uecho() { tooenc "$*"; } +function tooenc_() { + local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + echo_ "$src" + else + echo_ "$src" | iconv -f "$from" -t "$to" + fi +} +function uecho_() { tooenc_ "$*"; } +function toienc() { + local var_="$1" to_="${2:-$IENC}" from_="${3:-$UTOOLS_INPUT_ENCODING}" + if [ "$from_" != "$to_" ]; then + set_var "$var_" "$(iconv -f "$from_" -t "$to_" <<<"${!1}")" + fi +} +function uread() { + local var_ + [ $# == 0 ] && set -- REPLY + read "$@" + for var_ in "$@"; do + [ -z "$var_" -o "${var_:0:1}" == "-" ] && continue # ignorer les options + toienc "$var_" + done +} + +function stooenc() { + local from="${1:-$OENC}" to="${2:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + cat + else + iconv -f "$from" -t "$to" + fi +} +function stoolatin1() { stooenc "iso-8859-1"; } +function stoolatin9() { stooenc "iso-8859-15"; } +function stooutf8() { stooenc "utf-8"; } +function stoienc() { + local to="${1:-$IENC}" from="${2:-$UTOOLS_INPUT_ENCODING}" + if [ "$from" == "$to" ]; then + cat + else + iconv -f "$from" -t "$to" + fi +} +function stoilatin1() { stoienc "iso-8859-1"; } +function stoilatin9() { stoienc "iso-8859-15"; } +function stoiutf8() { stoienc "utf-8"; } + +# === fonctions pour contrôler l'affichage et l'interaction utilisateur === +# ces fonctions n'affiche le message que si le niveau de "verbosité" est +# suffisant: +# 4=afficher les messages de debug; 3=afficher les message informatifs; +# 2=afficher les warnings et les notes; 1=afficher les erreurs +# 0=ne rien afficher + +# niveau de "verbosité". on commence par défaut à 3 +MIN_VERBOSITY=0 +DEF_VERBOSITY=3 +MAX_VERBOSITY=4 +export VERBOSITY="${VERBOSITY:-$DEF_VERBOSITY}" +# niveau d'interaction. +MIN_INTERACTION=0 +DEF_INTERACTION=2 +MAX_INTERACTION=3 +export INTERACTION="${INTERACTION:-$DEF_INTERACTION}" + +function set_verbosity() { + # Configurer le niveau de verbosité ou d'interaction en fonction de la + # valeur de $1. Retourner 0 si l'argument a été utilisé, 1 sinon + + # $2 est la valeur d'un préfixe pour reconnaitre des options courtes + # supplémentaires. Par défaut, les options suivantes sont reconnues: -c, -q, + # -v, -Q, -D, ... Si le préfixe est -v par exemple, les options reconnues + # seront -vc, -vq, -vv, -vQ, -vD, ... *en plus* de -c, -q, -v, -Q, -D, ... + + local retval=0 + local enable_fake_arg= + local prefix="-" + + if [ "$1" = "--enable-fake-arg" ]; then + shift + enable_fake_arg=1 + fi + + [ -n "$2" ] && prefix="$2" + case "$1" in + # Ne changer, ni le niveau de verbosité, ni le niveau d'interaction, mais + # seulement si --enable-fake-arg a été activé + # Cette option permet à ask_yesno() et read_value() de spécifier les + # messages qui doivent être affichés dans le niveau par défaut de + # verbosité/interaction + -c|"${prefix}c"|--default) + [ -n "$enable_fake_arg" ] || retval=1 + ;; + + # Configurer le niveau de verbosité en fonction de l'argument: -q le + # diminue, -Q le met au minimum, -v l'augmente, -D le met au maximum + -q|"${prefix}q"|--quiet) + [ $VERBOSITY -gt $MIN_VERBOSITY ] && VERBOSITY=$(($VERBOSITY - 1)) + ;; + -v|"${prefix}v"|--verbose) + [ $VERBOSITY -lt $MAX_VERBOSITY ] && VERBOSITY=$(($VERBOSITY + 1)) + ;; + -Q|"${prefix}Q"|--very-quiet) + VERBOSITY=$MIN_VERBOSITY + ;; + -D|"${prefix}D"|--debug) + VERBOSITY=$MAX_VERBOSITY + ;; + + # Configurer le niveau d'interaction en fonction de l'argument: -b le met au + # minimum, -y le diminue, -i l'augmente + -b|"${prefix}b"|--batch) + # niveau d'interaction minimum + INTERACTION=$MIN_INTERACTION + ;; + -y|"${prefix}y"|--automatic) + # Dimininuer le niveau d'interaction + [ $INTERACTION -gt $MIN_INTERACTION ] && INTERACTION=$(($INTERACTION - 1)) + ;; + -i|"${prefix}i"|--interactive) + # Augmenter le niveau d'interaction. Augmenter aussi le niveau de verbosité + [ $INTERACTION -lt $MAX_INTERACTION ] && INTERACTION=$(($INTERACTION + 1)) + ;; + + *) + retval=1 + ;; + esac + return $retval +} + +function check_verbosity() { + # Teste si le niveau de verbosité/interaction courant est suffisant pour une + # opération dont le niveau est donné par $1 + + # Par exemple, 'check_verbosity -y' ne retourne vrai que si le niveau + # d'interaction courant est supérieur ou égal à ($DEF_INTERACTION - 1). Cela + # permet d'afficher un message ou d'effectuer une opération si on est au + # moins en mode automatic (par contre, en mode batch, l'opération ne sera + # pas effectué + + # De même 'check_verbosity -v' ne retourne vrai que si le niveau de + # verbosité courant est supérieur ou égal à ($DEF_VERBOSITY+1). Cela permet + # de n'afficher un message que si l'on est en mode verbose par exemple. + + # 'check_verbosity -c' retourne toujours vrai + + # Si $1 n'est pas un argument valide pour set_verbosity, alors on teste le + # niveau de verbosité ou d'interaction par défaut. si $2!="", alors on teste + # si le niveau de verbosité courant est supérieur à $2. Si $3!="", alors on + # teste sur le niveau d'interaction est supérieur à $3 + + # Si le premier argument est --action, alors on affiche "shift", "return", + # ou ":" suivant l'action à effectuer. Sinon, retourner 0 si le niveau est + # correct (Si $2="" et $3="", l'argument a été interprété et il faut faire + # un shift. Sinon l'argument a peut-être été interprété. On ne sait pas + # alors, sauf si on spécifie --action, s'il faut faire un shift ou non), 1 + # si le niveau n'est pas correct, mais l'argument $1 a été interprété (donc + # il faut faire un shift) et 2 si l'argument n'est pas correct (n'a pas été + # interprété) + + local verbosity="$VERBOSITY" interaction="$INTERACTION" + local VERBOSITY="$DEF_VERBOSITY" INTERACTION="$DEF_INTERACTION" + local action= + + if [ "$1" = "--action" ]; then + shift + action=1 + fi + + if set_verbosity --enable-fake-arg "$1"; then + shift + if [ $interaction -ne $INTERACTION -a $interaction -ge $INTERACTION ] || + [ $verbosity -ne $VERBOSITY -a $verbosity -ge $VERBOSITY ] || + [ $interaction -eq $INTERACTION -a $verbosity -eq $VERBOSITY ]; then + [ -n "$action" ] && echo shift + return 0 + else + [ -n "$action" ] && echo return + return 1 + fi + else + if [ -n "$2" ] && [ "$verbosity" -gt "$2" ]; then + [ -n "$action" ] && echo : + return 0 # ce cas n'est pas équivalent à celui ci-dessus (pas de shift à faire!) + elif [ -n "$3" ] && [ "$interaction" -gt "$3" ]; then + [ -n "$action" ] && echo : + return 0 # ce cas n'est pas équivalent à celui ci-dessus (pas de shift à faire!) + else + [ -n "$action" ] && echo return + return 2 + fi + fi +} + +function echo_() { echo -n "$*"; } + +# Couleurs +tty -s <&1 && NO_COLORS= || NO_COLORS=1 +function get_color() { + [ -n "$NO_COLORS" ] && return + [ -z "$*" ] && set RESET + + echo_ $'\e[' + local sep + while [ -n "$1" ]; do + [ -n "$sep" ] && echo_ ";" + case "$1" in + z|RESET) echo_ "0";; + o|BLACK) echo_ "30";; + r|RED) echo_ "31";; + g|GREEN) echo_ "32";; + y|YELLOW) echo_ "33";; + b|BLUE) echo_ "34";; + m|MAGENTA) echo_ "35";; + c|CYAN) echo_ "36";; + w|WHITE) echo_ "37";; + DEFAULT) echo_ "39";; + O|BLACK_BG) echo_ "40";; + R|RED_BG) echo_ "41";; + G|GREEN_BG) echo_ "42";; + Y|YELLOW_BG) echo_ "43";; + B|BLUE_BG) echo_ "44";; + M|MAGENTA_BG) echo_ "45";; + C|CYAN_BG) echo_ "46";; + W|WHITE_BG) echo_ "47";; + DEFAULT_BG) echo_ "49";; + @|BOLD) echo_ "1";; + -|FAINT) echo_ "2";; + _|UNDERLINED) echo_ "4";; + ~|REVERSE) echo_ "7";; + n|NORMAL) echo_ "22";; + esac + sep=1 + shift + done + echo_ "m" +} + +COULEUR_ROUGE="$(get_color RED BOLD)" +COULEUR_VERTE="$(get_color GREEN BOLD)" +COULEUR_JAUNE="$(get_color YELLOW BOLD)" +COULEUR_BLEUE="$(get_color BLUE BOLD)" +COULEUR_BLANCHE="$(get_color WHITE BOLD)" +COULEUR_NORMALE="$(get_color RESET)" + +# tester le niveau de "verbosité" ou d'interaction +function is_quiet() { [ $VERBOSITY -lt $DEF_VERBOSITY ]; } +function is_verbose() { ! is_quiet; } +function is_interactive() { [ $INTERACTION -ge $DEF_INTERACTION ]; } +function is_automatic() { [ $INTERACTION -lt $DEF_INTERACTION ]; } +function is_batch() { [ $INTERACTION -eq $MIN_INTERACTION ]; } + +# afficher une erreur *sur stderr* +function display_error() { [ $VERBOSITY -gt 0 ]; } +function eerror() { + eval `check_verbosity --action "$1" 0` + echo_ "${COULEUR_ROUGE}error:${COULEUR_NORMALE} " 1>&2 + tooenc "$*" 1>&2 +} + +# afficher un warning ou une note *sur stderr* +function display_warning() { [ $VERBOSITY -gt 1 ]; } +function ewarn() { + eval `check_verbosity --action "$1" 1` + echo_ "${COULEUR_JAUNE}warning:${COULEUR_NORMALE} " 1>&2 + tooenc "$*" 1>&2 +} +function enote() { + eval `check_verbosity --action "$1" 1` + echo_ "${COULEUR_VERTE}note:${COULEUR_NORMALE} " 1>&2 + tooenc "$*" 1>&2 +} + +# afficher une information +function display_info() { [ $VERBOSITY -gt 2 ]; } +function etitle() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}>>> " + tooenc_ "$*" + echo " <<<${COULEUR_NORMALE}" +} +function esubtitle() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}*** " + tooenc_ "$*" + echo "${COULEUR_NORMALE}" +} +function eimportant() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_ROUGE}important:${COULEUR_NORMALE} " + tooenc "$*" +} +function eattention() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_JAUNE}attention:${COULEUR_NORMALE} " + tooenc "$*" +} +function ecomment() { + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_VERTE}commentaire:${COULEUR_NORMALE} " + tooenc "$*" +} +function einfo() { + eval `check_verbosity --action "$1" 2` + tooenc "$*" +} +function einfon() { + eval `check_verbosity --action "$1" 2` + tooenc_ "$*" +} + +# afficher un message de debug +function is_debug() { [ $VERBOSITY -gt 3 ]; } +function edebug() { + eval `check_verbosity --action "$1" 3` + echo_ "${COULEUR_BLANCHE}debug:${COULEUR_NORMALE} " + tooenc "$*" +} +function edebugn() { + eval `check_verbosity --action "$1" 3` + echo_ "${COULEUR_BLANCHE}debug:${COULEUR_NORMALE} " + tooenc_ "$*" +} + +# Fonctions d'affichage pour suivre la progression d'opérations +function estep() { + # Une opération entière + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}*${COULEUR_NORMALE} " + tooenc "$*" +} +function estepn() { + # Une opération entière + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}*${COULEUR_NORMALE} " + tooenc_ "$*" +} + +function ebegin() { + # Le début d'une opération + eval `check_verbosity --action "$1" 2` + echo_ "${COULEUR_BLANCHE}*${COULEUR_NORMALE} " + tooenc_ "$*" + echo_ ":" + utools_ESTATUS_=0 + utools_edot_first_=1 +} + +function edot() { + # Une étape d'une opération commencée par ebegin, matérialisée par un "." + # Si l'argument est un fichier, utiliser -f pour que "$HOME" et "$(pwd)/" + # soient remplacés respectivement par "~" et "" (afin que les chemins soient + # plus courts) + # Si l'argument est un commentaire, utiliser -m pour le spécifier. -w + # indique que l'argument est un warning: un x jaune est affiché, avec le + # message associé si on est en mode debug. + # Sinon, l'argument est un status. 0 affiche un ".", sinon on affiche un "x" + local status=$? + local warn= color= + + if [ -n "$utools_edot_first_" ]; then + # au premier dot, afficher un espace d'abord + echo_ " " + utools_edot_first_= + fi + + eval `check_verbosity --action "$1" 2` + if is_debug; then + local msg paths path + if [ "$1" = "-f" ]; then + # chemins + shift + for path in "$@"; do + paths="${paths:+$paths,}$(ppath "$path")" + done + msg="$paths" + elif [ "$1" = "-m" -o "$1" = "-w" ]; then + # message + [ "$1" = "-w" ] && warn=1 # message de warning + shift + msg="$(tooenc "$*")" + else + # status + [ -n "$1" ] && status="$1" + fi + + [ "$utools_ESTATUS_" -eq 0 -a "$status" -ne 0 ] && utools_ESTATUS_="$status" + if [ "$status" -eq 0 -a -z "$warn" ]; then + echo_ "${msg:+ + }.${msg:+($msg)}" + else + color="$COULEUR_ROUGE" + [ -n "$warn" ] && color="$COULEUR_JAUNE" + echo_ "${msg:+ + }${color}x${COULEUR_NORMALE}${msg:+($msg)}" + fi + else + if [ "$1" = "-f" -o "$1" = "-m" -o "$1" = "-w" ]; then + [ "$1" = "-w" ] && warn=1 # message de warning + shift + else + [ -n "$1" ] && status="$1" + fi + + [ "$utools_ESTATUS_" -eq 0 -a "$status" -ne 0 ] && utools_ESTATUS_="$status" + if [ "$status" -eq 0 -a -z "$warn" ]; then + echo_ "." + else + color="$COULEUR_ROUGE" + [ -n "$warn" ] && color="$COULEUR_JAUNE" + echo_ "${color}x${COULEUR_NORMALE}" + fi + fi +} + +function ewait() { + # Une étape d'un opération commencée par ebegin, matérialisée par des "+" + # qui s'affichent tant que le processus de PID $1 tourne + # à utiliser de cette manière: + # ebegin "lancement de cmd" + # cmd & + # ewait $! + # eend + local action status + action="$(check_verbosity --action "$1" 2)" + status=$? # le status de check_verbosity + if [ "$action" == "return" ]; then # + # ne rien afficher, mais attendre quand même que le processus s'arrête + [ $status -eq 1 ] && shift + + local pid="$1" + [ -n "$pid" ] && wait "$pid" + return + fi + + eval "$action" + + if [ -n "$utools_edot_first_" ]; then + # au premier dot, afficher un espace d'abord + echo_ " " + utools_edot_first_= + fi + + local pid="$1" + local count=2 # attendre 2 secondes avant de commencer à afficher des "+" + [ -z "$pid" ] && return + + little_sleep # certains processus retournent tout de suite + while is_running "$pid"; do + sleep 1 + if [ $count -gt 0 ]; then + count=$(($count - 1)) + else + echo_ "+" + fi + done + echo_ "." # terminer par un "." +} + +function eend() { + # la fin d'une opération commencée par ebegin + local status=0 + + eval `check_verbosity --action "$1" 2` + + [ -n "$1" ] && status="$1" + [ "$utools_ESTATUS_" -eq 0 -a "$status" -ne 0 ] && utools_ESTATUS_="$status" + if [ "$utools_ESTATUS_" -eq 0 ]; then + echo " ${COULEUR_VERTE}ok${COULEUR_NORMALE}" + else + echo " ${COULEUR_ROUGE}error${COULEUR_NORMALE}" + fi + + status=$utools_ESTATUS_ + unset utools_ESTATUS_ + return $status +} + +# Arrêter le script avec un message d'erreur +function die() { [ -n "$*" ] && eerror "$@"; exit 1; } + +# === Nom du système === +export SYSTEM_NAME="`uname -s`" +beginswith "$SYSTEM_NAME" CYGWIN && SYSTEM_NAME=Cygwin +beginswith "$SYSTEM_NAME" MINGW32 && SYSTEM_NAME=Mingw +export SYSTEM_MACHINE="`uname -m`" + +if [ "$SYSTEM_NAME" == "Linux" ]; then + if [ -f /etc/debian_version ]; then + LINUX_FLAVOUR=debian + elif [ -f /etc/gentoo-release ]; then + LINUX_FLAVOUR=gentoo + elif [ -f /etc/redhat-release ]; then + LINUX_FLAVOUR=redhat + else + LINUX_FLAVOUR= + fi + if [ "$SYSTEM_MACHINE" == "x86_64" ]; then + SYSTEM_BITS=64 + else + SYSTEM_BITS=32 + fi +elif [ "$SYSTEM_NAME" == "Darwin" ]; then + MACOSX_VERSION="$(grep -A 1 CFBundleShortVersionString /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist | grep string | sed 's/.*//g +s/<\/string>.*$//g')" + MACOSX_NAME=UNSUPPORTED + if beginswith "$MACOSX_VERSION" 10.5; then + MACOSX_NAME=LEOPARD + elif beginswith "$MACOSX_VERSION" 10.4; then + MACOSX_NAME=TIGER + fi + SYSTEM_BITS= +fi + +# === vérifications === +[ -d "$TMPDIR" ] || ewarn "TMPDIR ($TMPDIR) n'existe pas" +##@inc]../../legacy/sysinc/base +__MAKE_SYSTEM_CAPS__="$(dirname "$0")/../../legacy/system_caps" +##@inc[../../legacy/sysinc/system_caps +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +##@require base + +# system_caps: Déterminer les capacités du syteme sur lequel nous tournons. Ces +# scripts sont prévus pour adapter un système non Linux pour qu'il fonctionne a +# peu près comme Linux. Les fonctions de base qui doivent être adaptées le sont +# en fonction du système sur lequel on tourne. Il suffit d'inclure ce fichier +# pour bénéficier des nouvelles fonctionnalités + +# note: sous Linux, ces scripts sont essentiellement un no-op + +__SYSTEM_CAPS_COMPUTED__= +if [ -z "$__MAKE_SYSTEM_CAPS__" -a -z "$__FORCE_COMPUTE_SYSTEM_CAPS__" -a -f "@@dest@@/legacy/system_caps" ]; then + source "@@dest@@/legacy/system_caps" +fi + +unset __FORCE_COMPUTE_SYSTEM_CAPS__ +if [ -z "$__SYSTEM_CAPS_COMPUTED__" ]; then + if [ -n "$__MAKE_SYSTEM_CAPS__" ]; then + >"$__MAKE_SYSTEM_CAPS__" + fi + + function __define_and_eval__() { + if [ -n "$__MAKE_SYSTEM_CAPS__" ]; then + echo "$1" >>"$__MAKE_SYSTEM_CAPS__" + fi + eval "$1" + } + + function __verify_system__() { + # Vérifier que le système est supporté + local OENC="$UTF8" + local system + for system in Linux SunOS Darwin Cygwin Mingw unsupported; do + [ "$system" = "$SYSTEM_NAME" ] && break + [ "$system" = "unsupported" ] && die "$SYSTEM_NAME: Système non supporté" + done + } + __verify_system__; unset -f __verify_system__ + + function __get_which_caps__() { + [ "$SYSTEM_NAME" = "Linux" ] && return + + # Obtenir les capacités de which: certaines implémentations affichent "no x + # in ..." sur stdout au lieu de l'afficher sur stderr + local OENC="$UTF8" + local tmpout="$TMPDIR/which_test.$$.out" + local tmperr="$TMPDIR/which_test.$$.err" + which __une_appli_qui_existe_pas__ >"$tmpout" 2>"$tmperr" + if [ -s "$tmpout" ]; then + __legacy_which__=1 # legacy + elif [ -s "$tmperr" ]; then + : # cas normal + else + __legacy_which__=2 # MacOSX 10.5 + fi + /bin/rm -f "$tmpout" + /bin/rm -f "$tmperr" + + if [ "$__legacy_which__" == 1 ]; then + local grep="${grep:-`which grep 2>&1`}" + if [ -x "$grep" ]; then + __define_and_eval__ 'function progexists() { test -n "$1" -a -z "$(which "$1" 2>&1 | '"$grep"' "^no $1")"; }' + else + die "grep est requis pour l'implémentation de progexists() sur ce système" + fi + elif [ "$__legacy_which__" == 2 ]; then + __define_and_eval__ 'function progexists() { which "$1" >&/dev/null; }' + fi + } + __get_which_caps__; unset -f __get_which_caps__ + + function __set_prog_func__() { + # Tester la présence d'un programme sur le système en cours, et si + # le programme doit être accedé avec un chemin absolu, créer une + # fonction du nom de ce programme qui pourra être utilisée pour + # appeler ce programme. + + # $1 == nom de la fonction a créer. Il s'agit géneralement du nom du + # programme, mais ce n'est pas une obligation. + + # $2 == nom à afficher si le programme n'est pas trouvé. Le message + # affiché sera de la forme "Ce script requière $2....". Si cette + # valeur est non vide, le programme *doit exister* sinon le script + # se termine. Si la valeur est "-", alors il s'agit de $1. + + # $3..$n == chemins dans lesquels il faut chercher le programme. Si + # la valeur est "-", on teste si le programme est disponible dans le + # path. + local OENC="$UTF8" + local prog="$1"; shift + local req="$1"; shift + + if [ -z "$prog" ]; then + die "USAGE: __set_prog_func__ prog_func req? path [paths...]" + fi + + if [ -n "${!prog}" ]; then + # Si une variable de ce nom est deja definie, on assume qu'il + # s'agit du chemin vers le programme + __define_and_eval__ 'function '"$prog"'() { '"${!prog}"' "$@"; }' + return 0 + fi + + local found= + while [ -n "$1" ]; do + local path="$1"; shift + + if [ "$path" = "--legacy" ]; then + # Declarer que les programmes qui suivent sont "legacy" + __define_and_eval__ 'export __legacy_'"$prog"'__=1' + continue + fi + + if [ "${path#/}" = "$path" ] && progexists "$path"; then + # Si on donne un nom de programme, et que ce programme se + # trouve dans le path, l'adopter avec son chemin absolu + if [ "$path" != "$prog" ]; then + path="$(type -p "$path")" + __define_and_eval__ 'function '"$prog"'() { "'"$path"'" "$@"; }' + fi + found=1 + break + fi + + if [ "$path" = "-" ] && progexists "$prog"; then + # pas la peine de creer une fonction, la commande existe + # deja avec ce nom-la dans le path + found=1 + break + fi + + if [ -d "$path" ]; then path="$path/$prog"; fi + if [ -d "$path" ]; then continue; fi + + if [ -x "$path" ]; then + __define_and_eval__ 'function '"$prog"'() { "'"$path"'" "$@"; }' + found=1 + break + fi + done + + if [ -z "$found" -a -n "$req" ]; then + if [ "$req" = "-" ]; then req="$prog"; fi + die "Ce script requière $req +Si ce programme est installé, faites + export $prog=/path/to/$prog +et relancez ce script" + fi + + test -n "$found" + } + + function __get_tools_path_and_caps__() { + [ "$SYSTEM_NAME" = "Linux" ] && return + + # Obtenir l'emplacement d'outils standards, tels que: + # cp, awk, grep, diff, tar, ps, date + local system + + # cp + for system in SunOS Darwin; do + [ "$SYSTEM_NAME" = "$system" ] && __define_and_eval__ 'export __legacy_cp__=1' + done + [ -n "$__legacy_cp__" ] && __define_and_eval__ 'function cp_a() { /bin/cp -pR "$@"; }' + + # awk + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ awk "gawk or nawk" \ + gawk \ + /usr/sfw/bin/gawk \ + /opt/sfw/bin/gawk \ + --legacy nawk \ + /usr/bin/nawk + + elif [ "$SYSTEM_NAME" = "Darwin" ]; then + # note: gensub() n'est supporté que sur gnuawk + # si __legacy_awk__ est défini, on sait que cette fonction n'est pas + # disponible + __set_prog_func__ awk "gawk or nawk" \ + gawk \ + /sw/bin/gawk \ + /sw/bin/awk \ + --legacy /usr/bin/awk + fi + + # grep + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ grep - \ + ggrep \ + /usr/sfw/bin/ggrep \ + /opt/sfw/bin/ggrep \ + --legacy /usr/bin/grep + fi + if [ -n "$__legacy_grep__" ]; then + __define_and_eval__ 'function quietgrep() { grep "$@" >&/dev/null; }' + fi + + # diff + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ diff - \ + gdiff \ + /usr/sfw/bin/gdiff \ + /opt/sfw/bin/gdiff \ + --legacy /usr/bin/diff + fi + if [ -n "$__legacy_diff__" ]; then + __define_and_eval__ 'function quietdiff() { diff "$@" >&/dev/null; }' + fi + + # tar + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ tar - \ + gtar \ + /usr/sfw/bin/gtar \ + /opt/sfw/bin/gtar \ + --legacy /usr/bin/tar + + elif [ "$SYSTEM_NAME" = "Darwin" ]; then + __set_prog_func__ tar - \ + gnutar gtar \ + /sw/bin/gtar \ + --legacy /usr/bin/tar + fi + + # ps + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ ps - \ + /usr/ucb/ps \ + --legacy /usr/bin/ps + __set_prog_func__ legacyps - \ + /usr/bin/ps + __define_and_eval__ 'function is_running() { test -n "$(legacyps -o pid -p "$1" | grep -v PID)"; }' + fi + if [ -n "$__legacy_ps__" ]; then + __define_and_eval__ 'function ps_all() { ps -ef; }' + fi + + # sleep + if [ "$SYSTEM_NAME" = "SunOS" -o "$SYSTEM_NAME" = "Darwin" ]; then + __define_and_eval__ 'function little_sleep() { n=1000; while [ $n -gt 0 ]; do n=$(($n - 1)); done; }' + fi + + # date + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __set_prog_func__ date - \ + gdate \ + /usr/sfw/bin/gdate \ + /opt/sfw/bin/gdate \ + --legacy /usr/bin/date + + elif [ "$SYSTEM_NAME" = "Darwin" ]; then + __set_prog_func__ date - \ + --legacy /bin/date + fi + + # readlink + if ! progexists readlink; then + __define_and_eval__ 'function readlink() { if [ -L "$1" ]; then /bin/ls -ld "$1" | awk '"'"'{print $11'"'"'}; fi; }' + fi + + # mktemp + if ! progexists mktemp; then + __define_and_eval__ 'function mktemp() { make_tempfile "$@"; }' + fi + + # curl/wget (pour dumpurl) + if ! progexists curl; then + if [ "$SYSTEM_NAME" = "SunOS" ]; then + if __set_prog_func__ curl "" /usr/local/bin/curl; then + __define_and_eval__ 'export __curl_EXISTS__=1' + fi + fi + fi + if ! progexists wget; then + if [ "$SYSTEM_NAME" = "SunOS" ]; then + if __set_prog_func__ wget "" /usr/sfw/bin/wget /opt/sfw/bin/wget; then + __define_and_eval__ 'export __wget_EXISTS__=1' + fi + fi + fi + + # is_root + if [ "$SYSTEM_NAME" = "SunOS" ]; then + __define_and_eval__ 'function is_root() { id | quietgrep "uid=0"; }' + + elif [ "$SYSTEM_NAME" = "Cygwin" -o "$SYSTEM_NAME" = "Mingw" ]; then + # on assume que sur ces systèmes, l'utilisateur est maitre à bord. + __define_and_eval__ 'function is_root() { true; }' + fi + + # sedi + if [ "$SYSTEM_NAME" = "Darwin" ]; then + __define_and_eval__ 'function __1sedi() { + local script="$1" input="$2" + if sed -i.bak "$script" "$input"; then + /bin/rm -f "$input.bak" + fi +} +function sedi() { + local script="$1" input + shift + for input in "$@"; do + __1sedi "$script" "$input" + done +}' + elif [ "$SYSTEM_NAME" = "SunOS" ]; then + __define_and_eval__ 'function __1sedi() { + local script="$1" input="$2" + if sed "$script" "$input" >"$input.out.$$"; then + chmod "$(/bin/ls -l "$input" | awk '\''function oct(mod) { + value = 0 + if (substr(mod, 1, 1) == "r") value = value + 4 + if (substr(mod, 2, 1) == "w") value = value + 2 + if (substr(mod, 3, 1) == "x") value = value + 1 + return value +} +{ + print oct(substr($1, 2, 3)) oct(substr($1, 5, 3)) oct(substr($1, 8, 3)) +}'\'')" "$input.out.$$" + /bin/mv -f "$input.out.$$" "$input" + fi +} +function sedi() { + local script="$1" input + shift + for input in "$@"; do + __1sedi "$script" "$input" + done +}' + fi + + # parse_date + if [ "$SYSTEM_NAME" = "Darwin" ]; then + function __pd_isleap() { + # tester si l'année $1 est bissextile + [ $(($1 % 4)) -eq 0 -a \( $(($1 % 100)) -ne 0 -o $(($1 % 400)) -eq 0 \) ] + } + function __pd_fix_month() { + # soit $1 le nom d'une variable contenant une année, et $2 le nom d'une + # variable contenant un mois, normaliser les valeurs de ces variables, en + # ajustant si nécessaire les valeurs. + local __pdfm_y="${!1}" __pdfm_m="${!2}" + let __pdfm_m=$__pdfm_m-1 + while [ $__pdfm_m -gt 11 ]; do + let __pdfm_m=$__pdfm_m-12 + let __pdfm_y=$__pdfm_y+1 + done + while [ $__pdfm_m -lt 0 ]; do + let __pdfm_m=$__pdfm_m+12 + let __pdfm_y=$__pdfm_y-1 + done + let __pdfm_m=$__pdfm_m+1 + eval "$1=$__pdfm_y; $2=$__pdfm_m" + } + __PD_MONTHDAYS=(0 31 28 31 30 31 30 31 31 30 31 30 31) + function __pd_monthdays() { + # calculer le nombre de jours du mois $2 de l'année $1 + local y="$1" m="$2" mds + __pd_fix_month y m + mds="${__PD_MONTHDAYS[$m]}" + [ "$m" -eq 2 ] && __pd_isleap "$y" && let mds=$mds+1 + echo $mds + } + function __pd_fix_day() { + # soit $1 le nom d'une variable contenant une année, $2 le nom d'une + # variable contenant un mois et $3 le nom d'une variable contenant un jour, + # normaliser les valeurs de ces variables, en ajustant si nécessaire les + # valeurs. cette fonction assume que la valeur de $2 est déjà corrigée avec + # __pd_fix_month + local __pdfd_y="${!1}" __pdfd_m="${!2}" __pdfd_d="${!3}" __pdfd_mds + let __pdfd_d=$__pdfd_d-1 + let __pdfd_mds=$(__pd_monthdays $__pdfd_y $__pdfd_m) + while [ $__pdfd_d -gt $(($__pdfd_mds-1)) ]; do + let __pdfd_d=$__pdfd_d-$__pdfd_mds + let __pdfd_m=$__pdfd_m+1 + __pd_fix_month __pdfd_y __pdfd_m + let __pdfd_mds=$(__pd_monthdays $__pdfd_y $__pdfd_m) + done + while [ $__pdfd_d -lt 0 ]; do + let __pdfd_m=$__pdfd_m-1 + __pd_fix_month __pdfd_y __pdfd_m + let __pdfd_d=$__pdfd_d-$(__pd_monthdays $__pdfd_y $__pdfd_m) + done + let __pdfd_d=$__pdfd_d+1 + eval "$1=$__pdfd_y; $2=$__pdfd_m; $3=$__pdfd_d" + } + function __pd_fix_date() { + # soit $1 le nom d'une variable contenant une année, $2 le nom d'une + # variable contenant un mois et $3 le nom d'une variable contenant un jour, + # normaliser les valeurs de ces variables, en ajustant si nécessaire les + # valeurs. + local __pdf_y="${!1}" __pdf_m="${!2}" __pdf_d="${!3}" + } + function parse_date() { + local value="$1" type="${2:-date}" + local d m y + # date courante + eval "$(date +%d/%m/%Y | awk -F/ '{ + print "d=" $1 "; m=" $2 "; y=" $3 + }')" + if [ "${value#+}" != "$value" ]; then + # ajouter $1 jours + d="$(($d+${value#+}))" + else + # parser une nouvelle date, en complétant avec les informations de la + # date du jour + eval "$(<<<"$value" awk -F/ "BEGIN { + dn=$dn; mn=$mn; yn=$yn + }"' + { + d = $1 + 0; if (d < 1) d = dn + 0; + m = $2 + 0; if (m < 1) m = mn + 0; + if ($3 == "") y = yn + 0; + else { y = $3 + 0; if (y < 100) y = y + 2000; } + print "d=" d "; m=" m "; y=" y + }')" + fi + # ensuite corriger les champs si nécessaire + __pd_fix_month y m + __pd_fix_day y m d + # enfin formater la date selon les volontés de l'utilisateur + case "$type" in + d|date) + awk "BEGIN { d=$d; m=$m; y=$y; "'printf "%02i/%02i/%04i\n", d, m, y }' + ;; + l|ldap) + awk "BEGIN { d=$d; m=$m; y=$y; "'printf "%04i%02i%02i000000+0400\n", y, m, d }' + ;; + esac + } + fi + } + __get_tools_path_and_caps__ + unset -f __set_prog_func__ __get_tools_path_and_caps__ + + __define_and_eval__ "__SYSTEM_CAPS_COMPUTED__=1" + unset -f __define_and_eval__ +fi +##@inc]../../legacy/sysinc/system_caps diff --git a/mediawiki b/mediawiki new file mode 100755 index 0000000..b2a831b --- /dev/null +++ b/mediawiki @@ -0,0 +1,958 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Outils pour gérer une installation de MediaWiki + +USAGE + $scriptname cmd [options] + +COMMANDES + newpage titre + Créer une nouvelle page avec le titre spécifié + newlog [titre] + Créer une nouvelle page datée du jour. Equivalent à newpage --log. + find filtre + Chercher les pages dont le nom correspondent au filtre + edit filtre + Editer la première page qui correspond au filtre + commit [msg] + Enregistrer les modifications dans le gestionnaire de version. + sync + Synchroniser le mediawiki vers la base de données. + generate srcdir + Générer la documentation du projet srcdir dans un espace de nom" +} + +SCRIPT_ALIASES=( + mwa:newpage + mwl:find + mwe:edit + mwci:commit + mwsync:sync +) + +if [ "$#" -eq 1 -a "$1" == --nutools-makelinks ]; then + # créer les liens + scriptname="$(basename "$0")" + for alias in "${SCRIPT_ALIASES[@]}"; do + alias="${alias%:*}" + ln -s "$scriptname" "$alias" + done + exit 0 +fi + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS awk || +exit 1 + +# Traduire le nom du script +for script_alias in "${SCRIPT_ALIASES[@]}"; do + splitpair "$script_alias" src dest + if [ "$scriptname" == "$src" ]; then + eval "set -- $dest \"\$@\"" + scriptname=mediawiki + break + fi +done + +REVSHA1="r.rev_sha1" +if [ -n "$FIXREVSHA1" ]; then + # Si l'instance de mediawiki attaquée est trop vielle, il n'y a pas le champ + # revision.rev_sha1 et une erreur se produit lors de l'accès à la base de + # données. Faire FIXREVSHA1=1 pour pouvoir au moins faire l'export des + # données avec init. + REVSHA1="'' AS rev_sha1" +fi + +MWCOMMIT=1 +set_defaults mediawiki + +function check_mwdir() { + [ -f "$1/.mediawiki" ] +} +function find_mwdir() { + # trouver le répertoire du mediawiki correspondant au répertoire $1 et + # retourner 0. Retourner 1 si $1 n'est pas dans un répertoire de mediawiki. + local mwdir="$(abspath "${1:-.}")" + while [ "$mwdir" != "/" ]; do + if check_mwdir "$mwdir"; then + echo "$mwdir" + return 0 + fi + mwdir="$(dirname "$mwdir")" + done + if [ -n "$MEDIAWIKIDIR" ] && check_mwdir "$MEDIAWIKIDIR"; then + echo "$(abspath "$MEDIAWIKIDIR")" + return 0 + fi + return 1 +} +function in_mwdir() { + # retourner 0 si le répertoire ou le fichier $1 est dans le répertoire d'un + # mediawiki. + local data mwdir + data="$(abspath "${1:-.}")" + mwdir="$(find_mwdir "$data")" || return 1 + [ "$data" == "mwdir" -o "${data#$mwdir/}" != "$data" ] && return 0 + return 1 +} + +function mw_local() { + echo "local MWDBHOST MWDBPORT MWDBUSER MWDBPASS MWDBNAME MWPREFIX MWSITEURL" +} +function load_mwconf() { + local mwdir="$1" + [ -f "$mwdir/.mediawiki" ] || die "Impossible de trouver le fichier de configuration" + source "$mwdir/.mediawiki" +} +function mwmysql() { + "$scriptdir/mysqlcsv" -h "$MWDBHOST" ${MWDBPORT:+-P "$MWDBPORT"} -u "$MWDBUSER" ${MWDBPASS:+-p"$MWDBPASS"} -n "$MWDBNAME" "$@" +} +function mwexportpages() { + mwmysql "\ +SELECT p.page_id, p.page_title, +r.rev_user, r.rev_user_text, r.rev_text_id AS rev_id, $REVSHA1, +t.old_id AS text_id, t.old_flags AS text_flags, t.old_text AS text_content +FROM ${MWPREFIX}page p +INNER JOIN ${MWPREFIX}revision r ON p.page_latest = r.rev_id +INNER JOIN ${MWPREFIX}text t ON r.rev_text_id = t.old_id +WHERE p.page_namespace = 0;" +} +function mwexportfiles() { + mwmysql "\ +SELECT p.page_id, p.page_title, +r.rev_user, r.rev_user_text, r.rev_text_id AS rev_id, $REVSHA1, +t.old_id AS text_id, t.old_flags AS text_flags, t.old_text AS text_content +FROM ${MWPREFIX}page p +INNER JOIN ${MWPREFIX}revision r ON p.page_latest = r.rev_id +INNER JOIN ${MWPREFIX}text t ON r.rev_text_id = t.old_id +WHERE p.page_namespace = 6;" +} + +function __ensure_mwdir() { + mwdir="$(find_mwdir "$mwdir")" || die "Impossible de trouver mediawiki. Etes-vous dans le bon répertoire?" + load_mwconf "$mwdir" +} + +function __check_exists() { + # vérifier si le fichier nommé $1 existe dans $mwdir + [ -n "$(find "$mwdir" -name "$(basename "$1")")" ] +} + +function __create() { + local mwdir="$1" file="$2" name="$3" title="$4" __newfiles="$5" __modfiles="$6" + + estepi "Création de la page $(ppath "$file")" + echo "# -*- coding: utf-8 mode: text -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +== $title == + +" >"$file" + echo "\"${name//\"/\\\"}\",,," >>"$mwdir/.mwpages" + [ -n "$__newfiles" ] && array_add "$__newfiles" "$file" + [ -n "$__modfiles" ] && array_add "$__modfiles" "$mwdir/.mwpages" + [ -n "$edit" ] && "${EDITOR:-vi}" +5 "$file" + return 0 +} + +function __strip_mwdir() { + if [ "$1" == "$mwdir" ]; then + echo . + else + echo "${1#$mwdir/}" + fi +} +function __commit() { + local mwdir="$1" msg="$2" + [ -d "$mwdir/.git" ] || return 0 + + local -a __newfiles __modfiles + [ -n "$3" ] && array_copy __newfiles "$3" + array_map __newfiles __strip_mwdir + [ -n "$4" ] && array_copy __modfiles "$4" + array_map __modfiles __strip_mwdir + + cwd="$(pwd)" + cd "$mwdir" + if [ -n "${__newfiles[*]}" -o -n "${__modfiles[*]}" ]; then + "$scriptdir/uproject" add "${__newfiles[@]}" "${__modfiles[@]}" + else + "$scriptdir/uproject" add . + fi + "$scriptdir/uproject" commit "$msg" "${__newfiles[@]}" "${__modfiles[@]}" + cd "$cwd" +} + +################################################################################ +function init_help() { + uecho +} +function __init_exportfile() { + local file="$1" + local md5="$(echo -n "$file" | md5sum)" + local url="$MWSITEURL/images/${md5:0:1}/${md5:0:2}/$file" + mkdir -p _media + curl -sko "_media/$file" "$url" +} +function __init_exportpage() { + local page_id="$1" page_title="$2" + local rev_user="$3" rev_user_text="$4" rev_id="$5" rev_sha1="$6" + local text_flags="$7" text_content="$8" + estep "$page_title" + echo "# -*- coding: utf-8 mode: text -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +$text_content" >"$page_title.txt" + [ -f .mwpages ] || touch .mwpages + [ -s .mwpages ] || echo "title,pageid,revid,hash" >.mwpages + echo "\"${page_title//\"/\\\"}\",$page_id,$rev_id,$rev_sha1" >>.mwpages +} +function init_cmd() { + eval "$(utools_local; mw_local)" + local mwdir force + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with init_help' \ + -d:,--mwdir: mwdir= \ + --force force=1 \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + [ -n "$mwdir" ] || mwdir=. + if [ ! -d "$mwdir" ]; then + ewarn "Le répertoire $mwdir n'existe pas" + ask_yesno "Voulez-vous le créer?" O || die + mkdir -p "$mwdir" || die + fi + if check_mwdir "$mwdir" && [ -z "$force" ]; then + eerror "Ce répertoire est déjà un export de MediaWiki. Utiliser --force" + return 1 + fi + if ! check_mwdir "$mwdir"; then + ask_yesno "$mwdir n'est pas un répertoire de mediawiki. Voulez-vous le configurer?" O || die + estep "Configuration de l'accès à la base de données" + read_value "Hôte du serveur MySQL" MWDBHOST localhost + read_value "Port du serveur MySQL" MWDBPORT 3306 + read_value "Compte utilisateur" MWDBUSER "$USER" + read_password "Mot de passe" MWDBPASS "" N + read_value "Nom de la base de données" MWDBNAME + read_value "Préfixe pour les tables" MWPREFIX "" N + estep "Configuration de l'accès au site" + read_value "URL du site MediaWiki" MWSITEURL http://localhost/mediawiki + local var + for var in MWDBHOST MWDBPORT MWDBUSER MWDBPASS MWDBNAME MWPREFIX MWSITEURL; do + set_var_cmd "$var" "${!var}" >>"$mwdir/.mediawiki" + done + fi + + __ensure_mwdir + cd "$mwdir" + + >.mwpages + etitle "Export des images" \ + eval "$(mwexportfiles | awkcsv -n -e '{ + print "__init_exportfile " quote_value(get("page_title")) +}' -a '')" + etitle "Export des pages" \ + eval "$(mwexportpages | awkcsv -e '{ + page_id = get("page_id") + page_title = get("page_title") + rev_user = get("rev_user") + rev_user_text = get("rev_user_text") + rev_id = get("rev_id") + rev_sha1 = get("rev_sha1") + text_flags = get("text_flags") + if (text_flags != "utf-8") continue + text_content = get("text_content") + gsub(/\\n/, "\n", text_content) + print "__init_exportpage " quote_value(page_id) " " quote_value(page_title) " " quote_value(rev_user) " " quote_value(rev_user_text) " " quote_value(rev_id) " " quote_value(rev_sha1) " " quote_value(text_flags) " " quote_value(text_content) +}' -a '')" +} + +################################################################################ +function newpage_help() { + uecho "$scriptname newpage: créer une nouvelle page de wiki + +USAGE + $scriptname newpage titre [-n nom] + +- titre est le titre de la nouvelle page utilisé pour créer le contenu initial. +- nom est le nom de base du fichier sur disque (utilisé aussi comme titre de la + page dans la base de données). Il est calculé à partir de titre. +Ces valeurs peuvent être spécifiées individuellement. + +OPTIONS + -d MWDIR + Spécifier le répertoire du mediawiki + -n NOM + Forcer le nom de base du fichier à utiliser au lieu de le calculer à + partir de titre. + --log + Dater la nouvelle page de la date du jour. Cette option est utile pour + documenter des opérations journalières. + -c + Ne pas lancer l'édition du fichier après l'avoir créé. Si MWCOMMIT=1, ne + pas lancer l'enregistrement des modifications dans le gestionnaire de + version." +} +function newpage_cmd() { + eval "$(utools_local; mw_local)" + local mwdir title log + local edit=1 + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with newpage_help' \ + -d:,--mwdir: mwdir= \ + -n:,--name:,-t:,--title: name= \ + -l,--log log=1 \ + -c,--noedit edit= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + __ensure_mwdir + in_mwdir || cd "$mwdir" + + title="$1" + if [ -z "$title" -a -z "$log" ]; then + read_value "Veuillez entrer un titre pour la page de wiki à créer" title + fi + + if [ -n "$name" ]; then + name="${name%.txt}" + else + name="$(echo "$title" | awk '{ + name = tolower($0) + gsub(/[^- /a-z0-9]/, "", name) + gsub(/\//, "-", name) + gsub(/ +/, "-", name) + print name +}')" + fi + if [ -n "$log" ]; then + title="$(date +"%d/%m/%Y")${title:+: $title}" + name="$(date +"%Y-%m-%d")${name:+-$name}" + fi + read_value "Veuillez confirmer le nom de base de la page" name "$name" + + local basename="$name" + local basefile="$name" + local file="$basefile.txt" + + if __check_exists "$file"; then + estepw "Le fichier $(ppath "$file") existe déjà." + ask_yesno "Si vous continuez, un ${COULEUR_JAUNE}NOUVEAU fichier${COULEUR_NORMALE} avec un suffixe numérique sera créé. +Sinon, vous pouvez utiliser '$scriptname edit' pour modifier le fichier existant. +Voulez-vous continuer?" N || return 1 + fi + local i=0 + while __check_exists "$file"; do + i=$(($i + 1)) + name="$basename-$i" + file="$basefile-$i.txt" + done + mkdirof "$file" + + local -a newfiles modfiles + __create "$mwdir" "$file" "$name" "$title" newfiles modfiles || return + if [ -n "$edit" -a -n "$MWCOMMIT" ]; then + __commit "$mwdir" "newpage $title --> $name" newfiles modfiles || return + else + estepi "mwci $(quoted_args "mwci newpage $title --> $name")" + fi + return 0 +} + +################################################################################ +function find_help() { + uecho "$scriptname find: trouver une page + +USAGE + $scriptname find + +OPTIONS + -d MWDIR + Spécifier le répertoire de mediawiki" +} +function find_cmd() { + eval "$(utools_local; mw_local)" + local mwdir + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with find_help' \ + -d:,--mwdir: mwdir= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + __ensure_mwdir + + local filter="$1" + find "$mwdir" -type f -name "*.txt" | + sed "s#^$mwdir/##g; s#.txt\$##g" | + csort | { + if [ -n "$filter" ]; then + grep -i "$filter" + else + cat + fi + } +} + +################################################################################ +function edit_help() { + uecho "$scriptname edit: modifier une page + +USAGE + $scriptname edit + +OPTIONS + -d MWDIR + Spécifier le répertoire de mediawiki" +} +function edit_cmd() { + eval "$(utools_local; mw_local)" + local mwdir + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with edit_help' \ + -d:,--mwdir: mwdir= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + __ensure_mwdir + + local filter="$1" found= + if [ -f "$filter" ]; then + local file="$(abspath "$filter")" + if [ "${file#$mwdir/}" != "$file" -a "${file%.txt}" != "$file" ]; then + page="${file#$mwdir/}" + page="${page%.txt}" + found=1 + fi + fi + if [ -z "$found" ]; then + local -a pages + array_from_lines pages "$(find_cmd -qq -d "$mwdir" "$filter")" + if [ "${#pages[*]}" -eq 0 ]; then + eerror "Aucune page de ce nom n'a été trouvée" + return 1 + elif [ "${#pages[*]}" -eq 1 ]; then + page="${pages[0]}" + else + simple_menu page pages -t "Pages trouvées" \ + -m "Veuillez choisir la page à éditer" -d "${pages[0]}" + fi + fi + + local -a newfiles modfiles + "${EDITOR:-vi}" "$mwdir/$page.txt" + array_add modfiles "$mwdir/$page.txt" + + if [ -n "$MWCOMMIT" ]; then + __commit "$mwdir" "edit ${page//\//:}" newfiles modfiles || return + else + estepi "mwci $(quoted_args "edit ${page//\//:}")" + fi + return 0 +} + +################################################################################ +function commit_help() { + uecho "$scriptname commit: enregistrer les modifications dans le gestionnaire de version + +USAGE + $scriptname commit + +OPTIONS + -d MWDIR + Spécifier le répertoire de mediawiki" +} +function commit_cmd() { + eval "$(utools_local; mw_local)" + local mwdir + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with commit_help' \ + -d:,--mwdir: mwdir= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + __ensure_mwdir + __commit "$mwdir" "$*" || return + return 0 +} + +################################################################################ +function sync_help() { + uecho "$scriptname sync: synchroniser les fichiers vers la base de données + +USAGE + $scriptname sync [destdir] + +OPTIONS + -d MWDIR + Spécifier le répertoire du mediawiki local" +} + +__SYNC_HEADERS="\ +fs_title,fs_path,fs_hash,fs_doublon\ +,local_title,local_pageid,local_revid,local_hash\ +,remote_pageid,remote_title,remote_revid,remote_hash,remote_content\ +,etat,action\ +" + +function __sync_action_fixme() { + eerror "Une ligne n'a pas pu être traitée. C'est probablement un bug +$__SYNC_HEADERS +$1" +} +function __sync_action_ignore() { + : +} +function __sync_action_create_local() { + local pageid="${9}" title="${10}" revid="${11}" hash="${12}" + local content="${13}" + content="${content//\\n/ +}" + + local file="$mwdir/$title.txt" + estepi "Création de la page $(ppath "$file")" + echo "# -*- coding: utf-8 mode: text -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +$content" >"$file" + #echo "\"${title//\"/\\\"}\",$pageid,$revid,$hash" >>"$mwdir/.mwpages" #XXX + #[ -n "$__newfiles" ] && array_add "$__newfiles" "$file" + #[ -n "$__modfiles" ] && array_add "$__modfiles" "$mwdir/.mwpages" +} +function __sync_action_create_remote() { + : +} +function __sync_action_update_remote() { + : +} +function __sync_action_resolve() { + : +} + +function __sync_ac_clean() { + ac_clean "$tmpinfos" "$fsinfos" "$dblinfos" "$localinfos" "$remoteinfos" "$mergedinfos" +} + +function sync_cmd() { + eval "$(utools_local; mw_local)" + local mwdir destdir noconf + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with sync_help' \ + -d:,--mwdir: mwdir= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + __ensure_mwdir + + local tmpinfos; ac_set_tmpfile tmpinfos + + ### Informations du système de fichier local + etitle "Système de fichier local" + local fsinfos; ac_set_tmpfile fsinfos + + estep "Calcul des informations" + find "$mwdir" -path "$mwdir/.git" -prune -o \ + -type d -name .svn -prune -o \ + -type f -name "*.txt" -print | + "$scriptdir/lib/b36sha1.py" --skip --mediawiki "$mwdir" \ + >"$tmpinfos" + + # Supprimer les doublons + # dblinfos contient la liste des fichiers en doublon + estep "Analyse des doublons" + local dblinfos; ac_set_tmpfile dblinfos + sortcsv -k title "$tmpinfos" | + awkcsv -v prevtitle= -v prevline= -v firstdbl:int=1 \ + -v dblinfos="$dblinfos" \ + -e '{ + title = get("title") + if (prevtitle != "") { + if (title != prevtitle) { + if (firstdbl) { # pour eviter d"afficher le dernier doublon + printheaders() + print prevline + } + firstdbl = 1 + } else { + if (do_once("dbl-show-headers")) { + array_printcsv(HEADERS, dblinfos) + } + if (firstdbl) { + firstdbl = 0 + printto(prevline, dblinfos) + + printheaders() + array_parsecsv(fields, prevline) + fields[geth("doublon")] = "1" + array_printcsv(fields) + } + printcsv(dblinfos) + } + } + formatcsv() + prevtitle = title + prevline = $0 +}' -a ' END { + if (prevtitle != "" && firstdbl) { + printheaders() + print prevline + } +}' >"$fsinfos" + + if [ -s "$dblinfos" ]; then + eerror "Vous avez des fichiers en doublon. Ces fichiers seront ignorés" + awkcsv <"$dblinfos" -e '{ print "! " get("path") }' -a '' + fi + + #echo === fsinfos; cat "$fsinfos" #XXX + eend + + ### Informations de la base de données locale + etitle "Base de données locale" + local localinfos; localinfos="$mwdir/.mwpages" + + #echo === localinfos; cat "$localinfos" #XXX + eend + + ### Informations de la base de données distante + etitle "Base de données distante" + local remoteinfos; ac_set_tmpfile remoteinfos + + mwexportpages | awkcsv -e '{ + # ne garder que les texte que l"on peut récupérer + text_flags = get("text_flags") + if (text_flags != "utf-8") next +}' | awkcsv \ + -m title:page_title,pageid:page_id,revid:rev_id,hash:rev_sha1,content:text_content \ + -k title,pageid,revid,hash,content \ + >"$remoteinfos" + + #echo === remoteinfos; cat "$remoteinfos" #XXX + eend + + ### Faire le rapprochement + etitle "Rapprochement des informations" + local mergedinfos; ac_set_tmpfile mergedinfos + + estep "Fusion des fichiers" + #XXX corriger la fusion des fichiers avec un script externe en python. le + # problème est la fusion de fs avec remote en cas de non existence de local + mergecsv -k title --lprefix fs_ "$fsinfos" --rprefix local_ "$localinfos" >"$tmpinfos" + mergecsv --lk local_title "$tmpinfos" --rk title --rprefix remote_ "$remoteinfos" | + awkcsv -k '*,etat,action' >"$mergedinfos" + + estep "Calcul des actions" + awkcsv <"$mergedinfos" -e ' +function toint(v, i) { + i = int(v + 0) + return i +} +function setea(etat, action) { + set("etat", etat) + set("action", action) +} +function setfixme() { + setea("@", "fixme") +} + +{ + fs_title = get("fs_title") + local_title = get("local_title") + remote_title = get("remote_title") + + ## traiter d"abord les fichiers à problème + # doublons + if (get("fs_doublon") != "") { + setea("%", "ignore") + } + + # fichiers perdus + else if (local_title != "" && fs_title == "") { + setea("!", "ignore") + } + + # fichiers gênants + else if (fs_title != "" && remote_title != "" && local_title == "") { + setea("~", "ignore") + } + + if (get("action") == "") { + # etats incohérents + fs_hash = get("fs_hash") + local_pageid = get("local_pageid") + local_revid = get("local_revid") + local_hash = get("local_hash") + remote_pageid = get("remote_pageid") + remote_revid = get("remote_revid") + remote_hash = get("remote_hash") + if (local_pageid == remote_pageid && local_revid == remote_revid && \ + local_hash != remote_hash) { + #XXX avertir utilisateur + local_hash = remote_hash + set("local_hash", remote_hash) + } + + ## puis traiter les états standards + # notregistered + if (fs_title != "" && local_title == "" && remote_title == "") { + setea("?", "ignore") + } + + # localnew + else if (fs_title != "" && fs_title == local_title && remote_title == "") { + setea("A", "create_remote") + } + + # remotenew + else if (fs_title == "" && local_title == local_title && remote_title != "") { + setea("N", "create_local") + } + + else if (fs_title != "" && fs_title == local_title && local_title == remote_title && \ + local_pageid == remote_pageid) { + # bothsame + if (local_revid == remote_revid && fs_hash == local_hash) { + setea("", "ignore") + } + + # localmodified + else if (local_revid == remote_revid && fs_hash != local_hash) { + setea("M", "update_remote") + } + + # remotemodified + else if (toint(remote_revid) > toint(local_revid) && fs_hash == local_hash) { + setea("U", "create_local") + } + + # bothconflict + else if (toint(remote_revid) > toint(local_revid) && fs_hash != local_hash) { + setea("C", "resolve") + } + + # inconnu + else { + setfixme() + } + } else { + setfixme() + } + } +}' >"$tmpinfos" + cat "$tmpinfos" >"$mergedinfos" + + # Afficher les resources dans un état spécial qui seront ignorées + awkcsv <"$mergedinfos" -e '{ + etat = get("etat") + action = get("action") + if (!etat || action != "ignore") next + + title = get("fs_path") + if (! title) title = get("local_title") + if (! title) title = get("remote_title") + print etat " " title +}' -a '' + + # Calculer les resources qui devront être traitées + awkcsv <"$mergedinfos" -e '{ + etat = get("etat") + action = get("action") + if (!etat || action == "ignore") next + + title = get("fs_path") + if (! title) title = get("local_title") + if (! title) title = get("remote_title") + print etat " " title +}' -a '' >"$tmpinfos" + + if [ ! -s "$tmpinfos" ]; then + estepi "Aucune modification à effectuer" + __sync_ac_clean + return 0 + fi + eend + + etitle "Synchronisation" + cat "$tmpinfos" + + if ask_yesno "Voulez-vous appliquer les modifications?" O; then + eval "$(awkcsv <"$mergedinfos" -e '{ + print "__sync_action_" get("action") " " quoted_values() +}' -a '')" + fi + + eend + + __sync_ac_clean +} + +################################################################################ +function generate_help() { + uecho "$scriptname generate: Générer la documentation d'un projet + +USAGE + $scriptname generate [options] srcdir + +Si srcdir contient un fichier .mediawikigen, ce fichier est sourcé pour générer +la documentation. Les fonction setpage() et addpage() sont disponible. + +OPTIONS + -d MWDIR + Spécifier le répertoire du mediawiki" +} +function generate_cmd() { + eval "$(utools_local; mw_local)" + local mwdir title + parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with generate_help' \ + -d:,--mwdir: mwdir= \ + -t:,--title: title= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + + __ensure_mwdir + + local srcdir="$(abspath "${1:-.}")" + [ -d "$srcdir" ] || die "Vous devez spécifier un répertoire de projet dont il faut générer la documentation" + local srcname=$(basename "$srcdir") + + [ -n "$title" ] || title="$srcname" + + function setpage() { + # Créer la page $1 avec le contenu de l'entrée standard + # $2 est un namespace local en dessous de $ns + local append + if [ "$1" == "--append" ]; then + append=1 + shift + fi + local name="$1" localns="$2" + + name="$(awk '{ +gsub(/_/, "") +print tolower($0) +}' <<<"$name")" + # XXX normaliser le nom: en plus de le mettre en minuscule, il faut + # supprimer certains caractères spéciaux comme '_'. en faire la liste? + + ns="$(norm_ns "$ns")" + [ -n "$localns" ] && local ns="$(norm_ns "$ns$localns")" + local basefile="$(get_pagesdir "$mwdir")/${ns//://}$name" + local file="$basefile.txt" + mkdirof "$file" + + if [ -f "$file" ]; then + local -a __newfiles + array_from_lines __newfiles "$(<"$newfiles")" + array_contains __newfiles "$file" || echo "$file" >>"$modfiles" + else + echo "$file" >>"$newfiles" + fi + if [ -n "$append" ]; then + cat >>"$file" + else + estepi "$(ppath "$file")" + cat >"$file" + fi + } + function addpage() { + # ajouter le contenu de l'entrée standard à la page $1 + setpage --append "$@" + } + function setmedia() { + # Créer le fichier de media $2 en copiant le contenu du fichier $1 + # $3 est un namespace local en dessous de $ns + local source="$1" name="$2" localns="$3" + + name="$(awk '{ +gsub(/_/, "") +print tolower($0) +}' <<<"$name")" + # XXX normaliser le nom: en plus de le mettre en minuscule, il faut + # supprimer certains caractères spéciaux comme '_'. en faire la liste? + + ns="$(norm_ns "$ns")" + [ -n "$localns" ] && local ns="$(norm_ns "$ns$localns")" + local file="$(get_mediadir "$mwdir")/${ns//://}$name" + mkdirof "$file" + + if [ -f "$file" ]; then + local -a __newfiles + array_from_lines __newfiles "$(<"$newfiles")" + array_contains __newfiles "$file" || echo "$file" >>"$modfiles" + else + echo "$file" >>"$newfiles" + fi + estepi "$(ppath "$file")" + cat "$source" >"$file" + } + function gendefault() { + setpage start <<<"===== $title ===== +" + if [ -f README.txt ]; then + { + awk 'NR==1 && $0 ~ /^#.*-\*-.*coding:/ {next} {print}' " + "$cmd" --help + echo "" + } | setpage "$cmdname" + if [ -n "$first" ]; then + addpage start <<<"==== Outils ====" + first= + fi + addpage start <<<" * [[$ns$cmdname]]" + done + eend + } + + local newfiles modfiles + ac_set_tmpfile newfiles + ac_set_tmpfile modfiles + ( + cd "$srcdir" + if [ -x .mediawikigen ]; then + ./.mediawikigen + elif [ -f .mediawikigen ]; then + source ./.mediawikigen + else + gendefault + fi + array_from_lines newfiles "$(<"$newfiles")" + array_from_lines modfiles "$(<"$modfiles")" + if [ -n "$MWCOMMIT" ]; then + __commit "$mwdir" "generate $srcdir" newfiles modfiles + else + estepi "mwci $(quoted_args "generate $srcdir")" + fi + ) +} + +################################################################################ + +parse_opts + "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +cmd="$1"; shift +case "$cmd" in +"") exit_with display_help;; +init) init_cmd "$@";; +newpage|createpage|addpage|page|p|new|n|create|c|add|a) newpage_cmd "$@";; +newlog|createlog|log|blog|date|d) newpage_cmd --log "$@";; +find|f|search|s|list|l) find_cmd "$@";; +edit|e|vim|vi) edit_cmd "$@";; +commit|ci) commit_cmd "$@";; +sync) sync_cmd "$@";; +generate|gen|g) generate_cmd "$@";; +__mwmysql) #XXX faire une requête SQL sur la base de données + __ensure_mwdir + mwmysql "$@" + ;; +*) die "$cmd: commande incorrecte";; +esac diff --git a/mkRewriteRules b/mkRewriteRules new file mode 100755 index 0000000..def6217 --- /dev/null +++ b/mkRewriteRules @@ -0,0 +1,291 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Créer un fichier de redirections pour Apache à partir d'un certain +nombre de règles + +USAGE + $scriptname -f rewrite.rules [-o RewriteRules.conf] [-w RewriteRules.html] host + +OPTIONS + -p Générer les directives et tenir compte de proxy_acls + Par défaut, le champ proxy_acls est ignoré + +FORMAT des règles de mapping"' +============================ + +Les commentaires commencent par le signe "#" +Les règles sont de la forme: + src:dest:host:suffix:OPTS:prot:proxy_acls + ^prefix + =literal + +prot vaut par défaut http. Il peut valoir aussi https + +Si dest ou suffix se terminent par $, on est en mode NO_SLASH +En mode NO_SLASH, si src se termine par $, on est en mode NO_TRAIL + +* Si dest est de la forme Application.woa +En mode NO_SLASH, on génère + RewriteRule ^/src(.*) [prot://host]/cgi-bin/WebObjects/dest[/suffix]$1 [L,OPTS] +En mode NO_SLASH+NO_TRAIL, on génère + RewriteRule ^/src [prot://host]/cgi-bin/WebObjects/dest[/suffix] [L,OPTS] +En mode normal, on génère + RewriteRule ^/src$ /src/ + RewriteRule ^/src/(.*) [prot://host]/cgi-bin/WebObjects/dest[/suffix]/$1 [L,OPTS] + +* Si dest n'\''est pas de la forme Application.woa +En mode NO_SLASH, on génère + RewriteRule ^/src(.*) [prot://host]/dest[/suffix]$1 [L,OPTS] +En mode NO_SLASH+NO_TRAIL, on génère + RewriteRule ^/src [prot://host]/dest[/suffix] [L,OPTS] +En mode normal, on génère + RewriteRule ^/src$ /src/ + RewriteRule ^/src/(.*) /dest[/suffix]/$1 [L,OPTS] + +Si une règle est précédée d'\''une ou plusieurs lignes de la forme "^prefix", +ces lignes sont copiées avant chacune des commandes RewriteRule générées +pour une règle. Ceci permet d'\''ajouter des conditions avec RewriteCond pour +une règle. e.g. + ^RewriteCond %{REMOTE_ADDR} 10\..* + src:dest.woa +qui génère: + RewriteCond %{REMOTE_ADDR} 10\..* + RewriteRule ^/src$ /src/ + RewriteCond %{REMOTE_ADDR} 10\..* + RewriteRule ^/src/(.*) /cgi-bin/WebObjects/dest.woa/$1 [L] + +Une ligne de la forme "=literal" est recopiée sans modifications (sans le "=") +dans le fichier de sortie. + +proxy_acls est utilisé si l'\''option -p est spécifiée et OPTS contient P (comme +proxy), ou si le mode de réécriture requière l'\''utilisation d'\''un proxy. + +* Avec la valeur "None", aucune directive n'\''est générée +* Si aucune valeur n'\''est spécifiée, la directive suivante est générée: + + AddDefaultCharset off + Order Deny,Allow + Allow from all + +* Si une valeur est spécifiée, la directive suivante est générée: + + AddDefaultCharset off + Order Allow,Deny + Allow from $proxy_acls + + +Dans les exemples donnés ci-dessus, $URL est l'\''url générée par la réécriture, +et $proxy_acls la valeur du champ proxy_acls spécifiée ci-dessus.' +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +proxy_enabled= +infile= +outfile="RewriteRules.conf" +htmlfile= +host= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -p proxy_enabled \ + -f: infile= \ + -o: outfile= \ + -w: htmlfile= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +[ -n "$infile" ] || die "Il faut spécifier le fichier de règles" +[ -f "$infile" ] || die "Fichier de règles non trouvé: $(ppath "$infile")" + +thishost="$1" +[ -n "$thishost" ] || die "Il faut spécifier l'hôte pour lequel créer le fichier de configuration" + +function has_proxy() { + # vérifier que les options $1 contiennent 'P' + local options + array_split options "$1" "," + array_contains options P +} + +if [ -n "$htmlfile" ]; then + echo ' + + + + +'"$thishost + + +

$thishost

+
    " >"$htmlfile" +fi + +>"$outfile" +array_from_lines rules "$(<"$infile" filter_comment)" +prefix= +for rule in "${rules[@]}"; do + if beginswith "$rule" ^; then + # Collecter les préfixe pour la règle suivante + prefix="${prefix:+$prefix +}${rule#^}" + continue + elif beginswith "$rule" =; then + # ligne litérale + echo "${rule#=}" >>"$outfile" + continue + fi + + IFS=:; set -- $rule; unset IFS + index=1 + done= + while [ -z "$done" ]; do + current="$1"; shift + while endswith "$current" "\\"; do + current="${current%\\}:$1"; shift + done + case $index in + 1) src="$current";; + 2) dest="$current";; + 3) host="$current";; + 4) suffix="$current";; + 5) options="$current";; + 6) prot="${current:-http}";; + 7) proxy_acls="$current";; + *) done=1;; + esac + index=$(($index + 1)) + done + + if [ "$thishost" == "$host" ]; then + host= + fi + + usrc="$src" + + trail=1 + if endswith "$src" '$'; then + trail= + usrc="${src%$}" + fi + + noslash= + if endswith "$suffix" '$'; then + noslash=1 + suffix="${suffix%$}" + fi + if endswith "$dest" '$'; then + noslash=1 + dest="${dest%$}" + fi + + proxy_url= + proxy_use= + + if endswith "$dest" .woa; then + # lien vers une application + if [ -n "$host" ]; then + # sur un autre hôte + if [ -n "$noslash" ]; then + echo "${prefix:+$prefix +}RewriteRule ^/$src${trail:+(.*)} $prot://$host/cgi-bin/WebObjects/$dest${suffix:+/$suffix}${trail:+\$1} [L${options:+,$options}]" >>"$outfile" + url="http://$thishost/$usrc" + proxy_url="$prot://$host/cgi-bin/WebObjects/$dest${suffix:+/$suffix}" + else + echo "${prefix:+$prefix +}RewriteRule ^/$src\$ /$src/" >>"$outfile" + echo "${prefix:+$prefix +}RewriteRule ^/$src/(.*) $prot://$host/cgi-bin/WebObjects/$dest${suffix:+/$suffix}/\$1 [L${options:+,$options}]" >>"$outfile" + url="http://$thishost/$usrc/" + proxy_url="$prot://$host/cgi-bin/WebObjects/$dest${suffix:+/$suffix}/" + fi + else + # sur le même hôte + if [ -n "$noslash" ]; then + echo "${prefix:+$prefix +}RewriteRule ^/$src${trail:+(.*)} /cgi-bin/WebObjects/$dest${suffix:+/$suffix}${trail:+\$1} [L,P${options:+,$options}]" >>"$outfile" + url="http://$thishost/$usrc" + proxy_use=1 + proxy_url="http://$thishost/cgi-bin/WebObjects/$dest${suffix:+/$suffix}" + else + echo "${prefix:+$prefix +}RewriteRule ^/$src\$ /$src/" >>"$outfile" + echo "${prefix:+$prefix +}RewriteRule ^/$src/(.*) /cgi-bin/WebObjects/$dest${suffix:+/$suffix}/\$1 [L,P${options:+,$options}]" >>"$outfile" + url="http://$thishost/$usrc/" + proxy_use=1 + proxy_url="http://$thishost/cgi-bin/WebObjects/$dest${suffix:+/$suffix}/" + fi + fi + else + # lien vers une url + if [ -n "$host" ]; then + # sur un autre hôte + if [ -n "$noslash" ]; then + echo "${prefix:+$prefix +}RewriteRule ^/$src${trail:+(.*)} $prot://$host/$dest${suffix:+/$suffix}${trail:+\$1} [L${options:+,$options}]" >>"$outfile" + url="http://$thishost/$usrc" + proxy_url="$prot://$host/$dest${suffix:+/$suffix}" + else + echo "${prefix:+$prefix +}RewriteRule ^/$src\$ /$src/" >>"$outfile" + echo "${prefix:+$prefix +}RewriteRule ^/$src/(.*) $prot://$host/$dest${suffix:+/$suffix}/\$1 [L${options:+,$options}]" >>"$outfile" + url="http://$thishost/$usrc/" + proxy_url="$prot://$host/$dest${suffix:+/$suffix}/" + fi + else + # sur le même hôte + if [ -n "$noslash" ]; then + echo "${prefix:+$prefix +}RewriteRule ^/$src${trail:+(.*)} /$dest${suffix:+/$suffix}${trail:+\$1}${options:+ [$options]}" >>"$outfile" + url="http://$thishost/$usrc" + proxy_url="http://$thishost/$dest${suffix:+/$suffix}" + else + echo "${prefix:+$prefix +}RewriteRule ^/$src\$ /$src/" >>"$outfile" + echo "${prefix:+$prefix +}RewriteRule ^/$src/(.*) /$dest${suffix:+/$suffix}/\$1${options:+ [$options]}" >>"$outfile" + url="http://$thishost/$usrc/" + proxy_url="http://$thishost/$dest${suffix:+/$suffix}/" + fi + fi + fi + has_proxy "$options" && proxy_use=1 + if [ -n "$proxy_enabled" -a -n "$proxy_use" ]; then + if [ "$proxy_acls" == "None" ]; then + : + elif [ -z "$proxy_acls" ]; then + echo "\ + + AddDefaultCharset off + Order Deny,Allow + Allow from all +" >>"$outfile" + else + echo "\ + + AddDefaultCharset off + Order Allow,Deny + Allow from $proxy_acls +" >>"$outfile" + fi + fi + + echo "" >>"$outfile" + if [ -n "$htmlfile" ]; then + echo "
  • $url
  • " >>"$htmlfile" + fi + + # Réinitialiser les préfixes pour chaque règle + prefix= +done + +if [ -n "$htmlfile" ]; then + echo '
+ +' >>"$htmlfile" +fi diff --git a/mkiso b/mkiso new file mode 100755 index 0000000..add594c --- /dev/null +++ b/mkiso @@ -0,0 +1,40 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: créer une image iso d'un répertoire + +USAGE + $scriptname [options] srcdir [dest.iso] + +OPTIONS + -M, --hfs + créer une image hybride ISO/HFS" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +hfsmode= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -M,--hfs hfsmode=1 \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +srcdir="$(abspath "${1:-.}")"; shift +srcname="$(basename "$srcdir")" +dest="$(abspath "${1:-$srcname.iso}")"; shift + +found= +for prog in genisoimage mkisofs; do + if progexists "$prog"; then + found=1 + break + fi +done +[ -n "$found" ] || die "Impossible de trouver genisoimage/mkisofs" + +if ask_yesno "$(ppath "$srcdir")/* --> $(ppath "$dest")" O; then + "$prog" -A "CD: $srcname" -V "$srcname" -r -hide-rr-moved -J ${hfsmode:+-hfs -part --netatalk} "$@" -o "$dest" "$srcdir" +fi diff --git a/mkurl b/mkurl new file mode 100755 index 0000000..af5b6a7 --- /dev/null +++ b/mkurl @@ -0,0 +1,71 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Enregistrer une url dans un fichier raccourci + +USAGE + $scriptname [output] + +OPTIONS +Par défaut, l'url est enregistrée dans un fichier homepage.url +Mais il est possible de spécifier un fichier avec l'extension .url pour un +raccourci utilisable aussi sous Windows, ou avec l'extension .desktop pour +compatibilité avec le standard XDG" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +function fix_urlfile() { + local urlfile="$1" + [ -d "$urlfile" ] && urlfile="$urlfile/homepage" + if [ "${urlfile%.url}" != "$urlfile" -o "${urlfile%.desktop}" != "$urlfile" ]; then + # le fichier a déjà l'extension. ne pas le modifier + : + else + # par défaut, utiliser l'extenion .url + urlfile="$urlfile.url" + fi + echo "$urlfile" +} + +url="$1" +urlfile="${2:-homepage}" +urlname= + +# trouver le fichier dans lequel enregistrer l'url +urlfile="$(fix_urlfile "$urlfile")" + +# si possible récupérer l'ancienne valeur +if [ -z "$url" -a -f "$urlfile" ]; then + url="$("$scriptdir/caturl" "$urlfile")" || die +fi + +# lire les nouvelles valeurs +read_value ${1:+-i} "Entrez l'url" url "$url" +read_value ${1:+-i} "Entrez le nom du fichier de raccourci" urlfile "$urlfile" + +# écrire l'url +urlfile="$(fix_urlfile "$urlfile")" +urlname="$(basename "$urlfile")" +urlname="${urlname%.*}" + +if [ "${urlfile%.url}" != "$urlfile" ]; then + echo "[InternetShortcut] +URL=$url" >"$urlfile" +elif [ "${urlfile%.desktop}" != "$urlfile" ]; then + echo "#!/usr/bin/env xdg-open +[Desktop Entry] +Encoding=UTF-8 +Version=1.0 +Type=Link +Name=$urlname +URL=$url +Icon=applications-internet" >"$urlfile" +fi diff --git a/mkusfx b/mkusfx new file mode 100755 index 0000000..96c008e --- /dev/null +++ b/mkusfx @@ -0,0 +1,129 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Créer une archive auto-extractible qui installe son contenu avec uinst + +USAGE + $scriptname [options] [--bare] src cmd... + $scriptname [options] [--uinst] src + +OPTIONS + --bare + Installer le contenu de l'archive en lançant la commande 'cmd...' avec + le répertoire courant étant le contenu de l'archive. Typiquement, ce + sera une commande de forme './script', où script est un fichier situé à + la racine de l'archive + Dans ce mode d'installation, l'option --self-contained est ignorée. + --uinst + Installer le contenu de l'archive avec uinst (par défaut) + -o dest + Spécifier le fichier de sortie. Par défaut, il s'agit de + \${src}-installer.run + --tmp-archive + Spécifier qu'il s'agit d'une archive temporaire. Cette archive + s'auto-détruit après utilisation. + --self-contained + Spécifier que l'archive doit pouvoir s'installer même sur un système sur + lequel nutools n'est pas installé. Cette archive contiendra une copie + locale de ulib et uinst.sh" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +MAKESELFDIR="$scriptdir/lib/makeself-2.1.5" + +sfxtype=uinst +mode=755 +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + --bare sfxtype=bare \ + --uinst sfxtype=uinst \ + -o: dest \ + -m: mode= \ + --tmp-archive tmp_archive \ + --self-contained self_contained \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +src="$(abspath "${1:-.}")" +srcname="$(basename "$src")" + +################################################################################ +if [ "$sfxtype" == "uinst" ]; then + ac_set_tmpdir archivedir + conf="$archivedir/uinst_prepare.conf" + + ## préparer l'installation + "$scriptdir/uinst" --prepare-1s "$archivedir" \ + --prepare-conf "$conf" \ + ${self_contained:+--prepare-with-ulib} \ + "$src" || die + + # la commande ci-dessus crée un fichier de configuration avec la variable + # prepare_name qui contient le *nom* du répertoire ou du fichier dans + # $archivedir à installer + source "$conf" + [ -n "$dest" ] || dest="${prepare_name}_installer.run" + + # si le fichier à générer se trouve dans le répertoire à installer, créer + # l'archive dans le répertoire au-dessus + dest="$(abspath "$dest")" + if [ "$src" == "$dest" -o "${dest#$src/}" != "$dest" ]; then + dest="$(dirname "$src")/$(basename "$dest")" + fi + + if [ -f "$dest" -a -z "$tmp_archive" ]; then + ask_yesno "Voulez-vous remplacer l'archive existante $(ppath "$dest")?" O || exit 0 + fi + + ## arguments pour la création de l'archive + args=("$MAKESELFDIR/makeself.sh" --quiet ${tmp_archive:+--tmp-archive} --nox11 + "$archivedir" "$dest" + "$srcname installer" + ) + + if [ -n "$self_contained" ]; then + args=("${args[@]}" /bin/sh ./uinst.sh --prepare-2s) + else + args=("${args[@]}" uinst) + fi + args=("${args[@]}" "$prepare_name") + +################################################################################ +elif [ "$sfxtype" == "bare" ]; then + shift + [ -n "$*" ] || die "Vous devez spécifier la commande à lancer pour installer l'archive" + [ -d "$src" ] || die "$src doit être un répertoire" + [ -n "$dest" ] || dest="${srcname}_installer.run" + + # si le fichier à générer se trouve dans le répertoire à installer, créer + # l'archive dans le répertoire au-dessus + dest="$(abspath "$dest")" + if [ "$src" == "$dest" -o "${dest#$src/}" != "$dest" ]; then + dest="$(dirname "$src")/$(basename "$dest")" + fi + + if [ -f "$dest" -a -z "$tmp_archive" ]; then + ask_yesno "Voulez-vous remplacer l'archive existante $(ppath "$dest")?" O || exit 0 + fi + + ## arguments pour la création de l'archive + args=("$MAKESELFDIR/makeself.sh" --quiet ${tmp_archive:+--tmp-archive} --nox11 + "$src" "$dest" + "$srcname installer" + "$@" + ) +fi + +################################################################################ +# créer l'archive + +estep "Création de l'archive $(ppath "$dest")" +"${args[@]}" + +# l'archive doit être exécutable +[ -n "$mode" ] && chmod "$mode" "$dest" + +exit 0 diff --git a/mocifs b/mocifs new file mode 100755 index 0000000..5e1329f --- /dev/null +++ b/mocifs @@ -0,0 +1,101 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Monter un partage Windows/Samba/CIFS + +USAGE + $scriptname [user@]host[/path] [mountpoint] + +Par défaut, le répertoire distant est montée sur un répertoire avec le même nom +de base que l'hôte. Si le répertoire distant est déjà monté, il est démonté. +Les options -M et -U permettent de modifier le comportement par défaut. + +OPTIONS + -M + Forcer le montage + -U + Forcer le démontage + -o OPTIONS + Ajouter les options spécifiées à la commande de montage mount.cifs + -u USERNAME + -p PASSWORD + -c USERNAME:PASSWORD + Spécifier les credentials à utiliser pour la connexion" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +if ! progexists mount.cifs; then + [ -x /sbin/mount.cifs ] || die "Ce script nécessite cifs-utils" +fi + +action=auto +options= +username= +password= +credentials= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -M action=mount \ + -U action=umount \ + -o:,--options: options= \ + -u:,--user: username= \ + -p:,--password:,--passwd: password= \ + -c:,--credentials: credentials= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +if is_root; then + sudo= +else + sudo=sudo + options="${options:+"$options,"}uid=$USER,gid=$USER,file_mode=0644,dir_mode=0755" +fi + +remote="${1#//}" +[ -n "$remote" ] || die "Vous devez spécifier l'hôte et le répertoire distant à monter" +splitfsep "$remote" / userhost path +userhost="${userhost%:}" +splituserhost "$userhost" cusername host +[ -n "$username" ] || username="$cusername" +[ -n "$username" ] || username="$USER" +[ -n "$host" ] || die "Vous devez spécifier l'hôte distant à monter" +[ -n "$path" ] || path='C$' + +if [ -n "$credentials" ]; then + splitpair "$credentials" cusername cpassword + [ -n "$cusername" ] && username="$cusername" + [ -n "$cpassword" ] && password="$cpassword" +fi +[ -n "$username" ] && options="${options:+"$options,"}user=$username" +[ -n "$password" ] && options="${options:+"$options,"}password=$password" + +mountpoint="$2" +[ -n "$mountpoint" ] || mountpoint="$host" +[ -n "$mountpoint" ] || mountpoint=cifs +mountpoint="$(abspath "$mountpoint")" + +if [ "$action" == "auto" ]; then + if mount | grep -q "$mountpoint"; then + action=umount + else + action=mount + fi +fi + +if [ "$action" == "umount" ]; then + if $sudo umount "$mountpoint"; then + echo "Volume $mountpoint démonté avec succès" + rmdir "$mountpoint" 2>/dev/null + exit 0 + fi +elif [ "$action" == "mount" ]; then + mkdir -p "$mountpoint" || die + if $sudo mount -t cifs "//$host/$path" "$mountpoint" ${options:+-o "$options"}; then + echo "Volume //$host/$path monté avec succès sur $(ppath "$mountpoint")" + exit 0 + fi +fi +exit 1 diff --git a/modav b/modav new file mode 100755 index 0000000..f1ce86e --- /dev/null +++ b/modav @@ -0,0 +1,115 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Monter un répertoire sur un hôte distant avec davfs + +USAGE + $scriptname http[s]://host[/path] [mountpoint] + +Par défaut, le répertoire distant est montée sur un répertoire avec le même nom +de base que l'hôte. Si le répertoire distant est déjà monté, il est démonté. +Les options -M et -U permettent de modifier le comportement par défaut. + +OPTIONS + -M + Forcer le montage + -U + Forcer le démontage + -o OPTIONS + Ajouter les options spécifiées à la commande de montage + -u USERNAME + -p PASSWORD + -c USERNAME:PASSWORD + Si les credentials à utiliser ne sont pas configuré dans le fichier + /etc/davfs2/secrets, cette option permet de les spécifier. Si cette + option est utilisée, il est possible de rajouter + ask_auth 0 + dans le fichier /etc/davfs2/davfs2.conf pour éviter le conflit qui se + produit quand des informations sont demandées interactivement. C'est le + cas par exemple si une connexion en https est faite et que la chaine de + certification n'est pas configurée. Pour mémoire, cela se fait avec + servercert myCAs.pem + dans le fichier /etc/davfs2/davfs2.conf + Bien entendu, il est préférable de configurer les credentials dans le + fichier /etc/davfs2/secrets avec la syntaxe + url username password + ou + mountpoint username password" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +if ! progexists mount.davfs; then + [ -x /usr/sbin/mount.davfs ] || die "Ce script nécessite davfs2" +fi + +action=auto +options= +username= +password= +credentials= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -M action=mount \ + -U action=umount \ + -o:,--options: options= \ + -u:,--user: username= \ + -p:,--password:,--passwd: password= \ + -c:,--credentials: credentials= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +if is_root; then + sudo= +else + sudo=sudo + options="${options:+"$options,"}uid=$USER,gid=$USER" +fi + +url="$1" +[ -n "$url" ] || die "Vous devez spécifier l'url à monter" +spliturl "$url" scheme cusername cpassword host port path +[ -n "$cusername" ] && username="$cusername" +[ -n "$cpassword" ] && password="$cpassword" + +if [ -n "$credentials" ]; then + splitpair "$credentials" cusername cpassword + [ -n "$cusername" ] && username="$cusername" + [ -n "$cpassword" ] && password="$cpassword" +fi +[ -n "$username" ] && options="${options:+"$options,"}username=$username" + +mountpoint="$2" +[ -n "$mountpoint" ] || mountpoint="$host" +[ -n "$mountpoint" ] || mountpoint=davfs +mountpoint="$(abspath "$mountpoint")" + +if [ "$action" == "auto" ]; then + if mount | grep -q "$mountpoint"; then + action=umount + else + action=mount + fi +fi + +if [ "$action" == "umount" ]; then + if $sudo umount "$mountpoint"; then + echo "Volume $mountpoint démonté avec succès" + rmdir "$mountpoint" 2>/dev/null + exit 0 + fi +elif [ "$action" == "mount" ]; then + mkdir -p "$mountpoint" || die + cmd=($sudo mount -t davfs "$url" "$mountpoint" ${options:+-o "$options"}) + if [ -n "$password" ]; then + echo "$password" | "${cmd[@]}" + else + "${cmd[@]}" + fi && { + echo "Volume $url monté avec succès sur $(ppath "$mountpoint")" + exit 0 + } +fi +exit 1 diff --git a/moiso b/moiso new file mode 100755 index 0000000..2a63c55 --- /dev/null +++ b/moiso @@ -0,0 +1,77 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Monter une image ISO + +USAGE + $scriptname image.iso [mountpoint] + +Par défaut, l'image iso est montée sur un répertoire avec le même nom de base. +Si l'image est déjà montée, elle est démontée. Les options -m et -u permettent +de modifier le comportement par défaut. + +OPTIONS + -m + Forcer le montage + -u + Forcer le démontage" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +action=auto +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -m action=mount \ + -u action=umount \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +image="$1" +[ -n "$image" ] || die "Vous devez spécifier l'image iso à monter" +[ -f "$image" ] || die "$image: fichier introuvable" +image="$(abspath "$image")" + +mountpoint="$2" +if [ -z "$mountpoint" ]; then + imagedir="$(dirname "$image")" + origname="$(basename "$image")" + basename="${origname%.*}" + if [ "$basename" == "$origname" ]; then + # fichier sans extension + i= + while [ -d "$imagedir/$basename.mp$i" ]; do + [ -n "$i" ] || i=0 + i=$(($i + 1)) + done + mountpoint="$imagedir/$basename.mp$i" + else + mountpoint="$imagedir/$basename" + fi +fi +mountpoint="$(abspath "$mountpoint")" +mkdir -p "$mountpoint" || die + +is_root && sudo= || sudo=sudo +if [ "$action" == "auto" ]; then + if mount | grep -q "$mountpoint"; then + action=umount + else + action=mount + fi +fi + +if [ "$action" == "umount" ]; then + if $sudo umount "$mountpoint"; then + echo "Volume démonté avec succès" + rmdir "$mountpoint" 2>/dev/null + exit 0 + fi +elif [ "$action" == "mount" ]; then + if $sudo mount -o loop "$image" "$mountpoint"; then + echo "Volume monté avec succès sur $(ppath "$mountpoint")" + exit 0 + fi +fi diff --git a/mossh b/mossh new file mode 100755 index 0000000..4b09667 --- /dev/null +++ b/mossh @@ -0,0 +1,134 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Monter un répertoire sur un hôte distant avec sshfs + +USAGE + $scriptname [user@]host[:/path] [mountpoint] + +Par défaut, le répertoire distant est montée sur un répertoire avec le même nom +de base que l'hôte. Si le répertoire distant est déjà monté, il est démonté. +Les options -M et -U permettent de modifier le comportement par défaut. + +OPTIONS + -M + Forcer le montage + -U + Forcer le démontage + -o OPTIONS + Ajouter les options spécifiées à la commande de montage + -s + Equivalent à -o allow_other ou -o allow_root selon que l'on est root ou + non + -u USER + Spécifier le user pour la connexion distante, s'il n'est pas possible de + le spécifier dans l'url. En cas de conflit, la valeur dans l'url est + prioritaire par rapport à cette option." +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +progexists sshfs || die "Ce script nécessite sshfs" + +action=auto +ssh=ssh +options= +shared= +user= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -M action=mount \ + -U action=umount \ + -S:,--ssh: ssh= \ + -o:,--options: options= \ + -s,--shared shared=1 \ + -l:,-u:,--user: user= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +if [ -n "$shared" ]; then + if is_root; then + options="${options:+"$options,"}allow_other" + else + options="${options:+"$options,"}allow_root" + fi +fi + +remote="$1" +[ -n "$remote" ] || die "Vous devez spécifier l'hôte et le répertoire distant à monter" + +# userhost représente une chaine de la forme user@host +# userhostc représente une chaine de la forme user@host[:[path1]] +# path1 représente un chemin sans slash, e.g. path +# path représente un chemin avec potentiellement des slashes, e.g. some[/path] +if [[ "$remote" == */* ]]; then + # remote est de la forme userhostc/[path] + splitfsep "$remote" / userhost path + if [ -n "$path" ]; then + # remote est de la forme userhostc/path + if [[ "$userhost" == *:* ]]; then + # remote est de la forme userhost:[path1]/path + # path=[path1]/path est pris tel quel + splitfsep "$userhost" : userhost ppath + path="$ppath/$path" + else + # remote est de la forme userhost/path + # path est pris AVEC le slash, pour faire un chemin absolu + path="/$path" + fi + else + # remote est de la forme userhostc/ + if [[ "$userhost" == *:* ]]; then + # remote est de la forme userhost:[path1]/ + # Si path1 est non vide, il est pris tel quel, mais le slash de fin + # est enlevé, car il est inutile. Sinon, prendre le slash uniquement + # parce que cela signifie que l'on veut monter le répertoire racine + # distant. + splitfsep "$userhost" : userhost ppath + [ -n "$ppath" ] || ppath=/ + path="$ppath" + else + # remote est de la forme userhost/ + # path est pris AVEC le slash, pour faire un chemin absolu + path=/ + fi + fi +else + # remote est de la forme userhost[:[path1]] + splitfsep "$remote" : userhost path +fi + +splituserhost "$userhost" cuser host +[ -n "$user" ] || user="$cuser" +[ -n "$user" ] || user="$USER" +[ -n "$host" ] || die "Vous devez spécifier l'hôte distant à monter" + +mountpoint="$2" +[ -n "$mountpoint" ] || mountpoint="$host" +mountpoint="$(abspath "$mountpoint")" +mkdir -p "$mountpoint" || die + +if [ "$action" == "auto" ]; then + if mount | grep -q "$mountpoint"; then + action=umount + else + action=mount + fi +fi + +if [ "$action" == "umount" ]; then + if fusermount -u "$mountpoint"; then + echo "Volume $mountpoint démonté avec succès" + rmdir "$mountpoint" 2>/dev/null + exit 0 + fi +elif [ "$action" == "mount" ]; then + if sshfs "$user@$host:$path" "$mountpoint" -o ssh_command="$ssh"${options:+",$options"}; then + echo "Volume $user@$host:$path monté avec succès sur $(ppath "$mountpoint")" + exit 0 + else + rmdir "$mountpoint" 2>/dev/null + fi +fi diff --git a/mysqlcsv b/mysqlcsv new file mode 100755 index 0000000..29a81b9 --- /dev/null +++ b/mysqlcsv @@ -0,0 +1,131 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Faire une requête MySQL et formater la sortie pour traitement avec awkcsv + +USAGE + $scriptname [db [query]] [-- mysql options] + +db est le nom de la base de données +query est la requête sql à exécuter. Si query n'est pas spécifiée, la(les) + requête(s) sql sont prises sur l'entrée standard, ou depuis un fichier + si l'option -f est spécifiée. + +OPTIONS + -h host + -P port + -u user + -ppassword + Informations de connexion à la base de données + -C CONFIG + Prendre les informations de connexion depuis le fichier spécifié. + Le fichier doit être de la forme + host=HOST + #post=3306 + user=USER + password=PASS + #database=DB + #query=QUERY + Les variables port, database et query sont facultatives. + Les valeurs définies dans ce fichier sont prioritaires par rapport à + celles qui auraient été spécifiées sur la ligne de commande. + Utiliser password=--NOT-SET-- s'il faut se connecter sans mot de passe + Cette option peut être utilisée plusieurs fois, auquel cas les fichiers + sont chargés dans l'ordre. + --profile PROFILE + La variable \$PROFILE est définie avec la valeur spécifiée avant de + sourcer les fichiers de configuration. Cela permet d'avoir des fichiers + de configuration qui calculent dynamiquement les paramètres en fonction + de la valeur du profil. + -N + Ne pas afficher les en-têtes + -c + Continuer le traitement même en cas d'erreur + -r + Ne pas autoriser mysql à mettre en échappement certaines valeurs + retournées par le serveur. Par défaut, les transformations suivantes + sont effectuées: + newline --> \\n + tab --> \\t + nul --> \\0 + \\ --> \\\\ + -n + Transformer dans le flux en sortie les valeurs NULL en chaines vides + -f INPUT + Lire la requête depuis le fichier input au lieu de le lire depuis la + ligne de commande ou l'entrée standard. Ne pas spécifier cette option + ou utiliser '-' pour lire depuis l'entrée standard. + Cette option est ignorée si la requête est spécifiée parmi les + arguments." +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +host= +port= +user= +password=--NOT-SET-- +configs=() +profile= +noheaders= +force= +raw= +fixnulls= +input= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -h:,-H:,--host: host= \ + -P:,--port: port= \ + -u:,--user: user= \ + -p::,--passwd:,--password: password= \ + -C:,--config: configs \ + --profile: profile= \ + -N,--no-headers,--skip-column-names noheaders=1 \ + -c,--force force=1 \ + -r,--raw raw=1 \ + -n,--nulls,--fix-nulls fixnulls=1 \ + -f:,--input: input= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +database="$1"; shift +query="$1"; shift +[ "$1" == "--" ] && shift + +if [ -n "${configs[*]}" ]; then + PROFILE="$profile" + array_fix_paths configs + for config in "${configs[@]}"; do + [ -f "$config" ] || die "Fichier introuvable: $config" + source "$(abspath "$config")" + done +fi + +mysqlcmd='mysql "${mysqlargs[@]}" "$@"' +mysqlargs=( + -B --default-character-set=utf8 + ${host:+-h "$host"} ${port:+-P "$port"} + ${user:+-u "$user"} + ${noheaders:+-N} ${force:+-f} ${raw:+-r} + ${database:+-D "$database"} + ${query:+-e "$query"} +) +if [ "$password" != "--NOT-SET--" ]; then + mysqlargs=("${mysqlargs[@]}" -p"$password") +fi + +if [ -z "$query" -a -n "$input" -a "$input" != "-" ]; then + mysqlcmd="<$(quoted_arg "$input") $mysqlcmd" +fi + +eval "$mysqlcmd" | +awkrun -f FS="$TAB" fixnulls:int="$fixnulls" '{ + if (fixnulls) { + for (i = 1; i <= NF; i++) { + if ($i == "NULL") $i = "" + } + } + printcsv() +}' diff --git a/mysqlloadcsv b/mysqlloadcsv new file mode 100755 index 0000000..d4c9ecc --- /dev/null +++ b/mysqlloadcsv @@ -0,0 +1,147 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Charger une table MySQL avec un fichier csv + +USAGE + $scriptname [db.]table [fields...] [-- mysql options] + +db est le nom de la base de données +table est le nom de la table à charger +fields est la liste des colonnes. Si cette valeur est spécifiée, il faudra + peut-être utiliser l'option -s pour ignorer le cas échéant la ligne des + en-têtes dans le fichier en entrée. Sinon, les colonnes à utiliser sont + calculées à partir du fichier en entrée. + +Dans les données en entrées, qui doivent être en UTF8, les conversions suivantes +sont effectuées: + + \\0 --> NUL + \\b --> backspace + \\n --> newline + \\r --> carriage return + \\t --> tab + \\Z --> Ctrl+Z + \\N --> NULL + +OPTIONS + -h host + -P port + -u user + -ppassword + Informations de connexion à la base de données + -C CONFIG + Prendre les informations de connexion depuis le fichier spécifié. + Le fichier doit être de la forme + host=HOST.TLD + #post=3306 + user=USER + password=PASS + #dbtable=DB.TABLE + #fields=(FIELDS...) + # Il est possible aussi de spécifier DB et TABLE séparément: + #database=DB + #table=TABLE + Les variables port, dbtable et fields sont facultatives. + Les valeurs définies dans ce fichier sont prioritaires par rapport à + celles qui auraient été spécifiées sur la ligne de commande. + Utiliser password=--NOT-SET-- s'il faut se connecter sans mot de passe + Cette option peut être utilisée plusieurs fois, auquel cas les fichiers + sont chargés dans l'ordre. + --profile PROFILE + La variable \$PROFILE est définie avec la valeur spécifiée avant de + sourcer les fichiers de configuration. Cela permet d'avoir des fichiers + de configuration qui calculent dynamiquement les paramètres en fonction + de la valeur du profil. + -f INPUT + Fichier en entrée. Ne pas spécifier cette option ou utiliser '-' pour + lire depuis l'entrée standard. + -s NBLINES + Nombre de lignes à sauter dans le fichier en entrée" +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS awk || +exit 1 + +host= +port= +user= +password=--NOT-SET-- +configs=() +profile= +input= +skip_lines= +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + -h:,-H:,--host: host= \ + -P:,--port: port= \ + -u:,--user: user= \ + -p::,--passwd:,--password: password= \ + -C:,--config: configs \ + -f:,--input: input= \ + -s:,--skip-lines: skip_lines= \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +dbtable="$1"; shift +fields=() +while [ "$#" -gt 0 -a "$1" != "--" ]; do + fields=("${fields[@]}" "$1") + shift +done +[ "$1" == "--" ] && shift + +splitname "$dbtable" database table +if [ -z "$table" ]; then + table="$database" + database= +fi + +if [ -n "${configs[*]}" ]; then + PROFILE="$profile" + array_fix_paths configs + for config in "${configs[@]}"; do + [ -f "$config" ] || die "Fichier introuvable: $config" + source "$(abspath "$config")" + done +fi +[ -n "$table" ] || die "Vous devez spécifier la table dans laquelle se fera l'importation" + +isnum "$skip_lines" || skip_lines=0 + +if [ -n "${fields[*]}" ]; then + # nous savons quels champs utiliser + cfields="$(array_join fields ,)" + if [ -z "$input" -o "$input" == "-" ]; then + ac_set_tmpfile input + awkcsv -s "$skip_lines" -k "$cfields" >"$input" + else + ac_set_tmpfile tmpinput + <"$input" awkcsv -s "$skip_lines" -k "$cfields" >"$tmpinput" + input="$tmpinput" + fi +else + # les champs seront calculés à partir de l'entrée + if [ -z "$input" -o "$input" == "-" ]; then + ac_set_tmpfile input + cat >"$input" + fi + array_split fields "$(awkcsv -s "$skip_lines" -e '{ print array_join(HEADERS, ","); exit }' <"$input")" "," + cfields="$(array_join fields ,)" + skip_lines=$(($skip_lines + 1)) +fi + +mysqlargs=( + ${host:+-h "$host"} ${port:+-P "$port"} + ${user:+-u "$user"} +) +[ "$password" != "--NOT-SET--" ] && mysqlargs=("${mysqlargs[@]}" -p"$password") +mysqlargs=("${mysqlargs[@]}" + "$database" + "load data local infile '$input' into table \`$table\` character set 'utf8' fields terminated by ',' optionally enclosed by '\\\"' escaped by '\\\\' lines terminated by '\\n' starting by '' ignore $skip_lines lines ($cfields);" + -- + --local-infile=1 +) + +exec "$scriptdir/mysqlcsv" "${mysqlargs[@]}" "$@" diff --git a/openurl b/openurl new file mode 100755 index 0000000..db72e7f --- /dev/null +++ b/openurl @@ -0,0 +1,33 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +function display_help() { + uecho "$scriptname: Ouvrir une URL dans un navigateur + +USAGE + $scriptname " +} + +source "$(dirname "$0")/ulib/ulib" && +urequire DEFAULTS || +exit 1 + +parse_opts "${PRETTYOPTS[@]}" \ + --help '$exit_with display_help' \ + @ args -- "$@" && set -- "${args[@]}" || die "$args" + +url="$("$scriptdir/caturl" "$1")" || die + +if progexists xdg-open; then + exec xdg-open "$url" +elif progexists gnome-open; then + exec gnome-open "$url" +else + # si on est sur un MacOSX, essayer utiliser open + urequire sysinfos + if check_sysinfos -s macosx; then + exec open "$url" + fi +fi + +die "Impossible de trouver une méthode pour ouvrir l'url $url" diff --git a/profile b/profile new file mode 100644 index 0000000..04cae2b --- /dev/null +++ b/profile @@ -0,0 +1,8 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +[ -z "$USER" -a -n "$LOGNAME" ] && export USER="$LOGNAME" + +function uprovide() { :; } +source @@dest@@/ulib/uenv || return +__uenv_source_dirs @@dest@@/profile.d "$HOME/etc/profile.d" +__uenv_cleanup diff --git a/pyulib/.project b/pyulib/.project new file mode 100644 index 0000000..d4d9363 --- /dev/null +++ b/pyulib/.project @@ -0,0 +1,17 @@ + + + pyulib + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/pyulib/.pydevproject b/pyulib/.pydevproject new file mode 100644 index 0000000..11972fa --- /dev/null +++ b/pyulib/.pydevproject @@ -0,0 +1,10 @@ + + + + + +/pyulib/src + +python 2.6 +python + diff --git a/pyulib/.settings/org.eclipse.core.resources.prefs b/pyulib/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..b5539e6 --- /dev/null +++ b/pyulib/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Tue Oct 26 14:09:51 RET 2010 +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/pyulib/.settings/org.eclipse.core.runtime.prefs b/pyulib/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..4a39cf4 --- /dev/null +++ b/pyulib/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,3 @@ +#Tue Oct 26 14:09:51 RET 2010 +eclipse.preferences.version=1 +line.separator=\n diff --git a/pyulib/.udir b/pyulib/.udir new file mode 100644 index 0000000..d6086c8 --- /dev/null +++ b/pyulib/.udir @@ -0,0 +1,6 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Utiliser 'udir --help-vars' pour une description de la signification des +# variables suivantes: +udir_desc="Librairies python de utools" +udir_note="" +udir_types=("uinst:python") diff --git a/pyulib/MANIFEST.in b/pyulib/MANIFEST.in new file mode 100644 index 0000000..73dd970 --- /dev/null +++ b/pyulib/MANIFEST.in @@ -0,0 +1,4 @@ +global-include * +global-exclude *.pyc +exclude MANIFEST +prune dist diff --git a/pyulib/devel/TODO.txt b/pyulib/devel/TODO.txt new file mode 100644 index 0000000..1156bfd --- /dev/null +++ b/pyulib/devel/TODO.txt @@ -0,0 +1,5 @@ +# -*- coding: utf-8 mode: text -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +[ ] migrer spp + [ ] comment forcer le chargement des librairies dans pyulib/ plutôt que + dans le répertoire installé diff --git a/pyulib/devel/Tasks.txt b/pyulib/devel/Tasks.txt new file mode 100644 index 0000000..adc7811 --- /dev/null +++ b/pyulib/devel/Tasks.txt @@ -0,0 +1,72 @@ +## -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +## sprops: {lastid=70 serial=136 type=t} +Créer un nouveau type de tâche: e comme event.\n\nSe comporte comme une tâche de type p, sauf que la tâche est automatiquement marquée comme faite à la date indiquée.\nCe type sert à suivre des événements sur lesquels on n'a pas de pouvoir (par opposition à une tâche que l'on doit effectuer. {tid=1 cdate=27/01/2009 ddate=06/02/2009} +Utiliser -s ou -S sur la ligne de commande prend ~/.utools comme répertoire de base pour exprimer les fichiers. {tid=2 cdate=27/01/2009 ddate=08/02/2009} +L'état de la tâche représente son état au moment ou la requête est effectuée, pas son état possible dans le futur.\n\nPar exemple, une tâche planifiée peut être done, et dans le futur être planned, mais au moment de l'appel de la méthode, elle est done {tid=3 cdate=27/01/2009 ddate=06/02/2009} +title et body ne sont pas correctement splittés si on donne le body lors de 'tasks.py add' {tid=4 cdate=27/01/2009 ddate=10/02/2009} +'pnew path/comp.wosrc' ne semble pas pouvoir générer les fichiers dans path {tid=5 cdate=30/01/2009 ddate=13/09/2009} +pnew pourrait prendre une configuration par défaut dans un fichier du projet, par exemple $projdir/.utools/projectrc.\n\nCeci permettrait de configurer pour un projet des valeurs par défaut comme encoding, paramètres des templates, etc. {tid=6 cdate=30/01/2009 type=i} +Il semble que la recherche ne se fait pas si l'id est de la forme SID.TID\n\nPar exemple: 'tasks.py list 3.1' n'affichera pas la tâche d'id 3.1\nEn fait, c'est dû au bug résolu par #11. {tid=7 cdate=31/01/2009 ddate=10/02/2009} +Dans PlanStrategy, une tâche est _done si elle a été faite le jour même. De même, il faudrait qu'elle soit showable le jour ou elle a été effectuée, même si mindate est dans le futur. {tid=8 cdate=03/02/2009 ddate=06/02/2009} +get_urgency() doit afficher si une tâche est prêt d'être obsolete {tid=9 cdate=06/02/2009 ddate=08/02/2009} +Trier les tâches lors de leur affichage {tid=10 cdate=07/02/2009 ddate=08/02/2009} +'tl ' implique -a {tid=11 cdate=08/02/2009 ddate=10/02/2009} +(ABANDONNE) 'tasks.py -g' reste dessous en temps normal et se place au dessus s'il est activé {tid=12 cdate=08/02/2009 ddate=11/08/2009} +Implémenter l'auto-filtre pour les projets\n\n* autofiltre pour la recherche\n* autofiltre pour la création\n** Il faut que le store puisse savoir qu'il est dans un répertoire de projet, et\najoute automatiquement +project dans la liste des projets.\n** Ou alors, ce projet n'est pas écrit dans le store sur le disque. Il existe\ndynamiquement du fait que le store se trouve dans le répertoire du projet. A ce\nmoment-là, pas besoin d'autofiltre pour la création. {tid=13 cdate=10/02/2009} +edition d'une tâche {tid=14 cdate=10/02/2009 ddate=11/02/2009} +l'option -d permet de n'afficher que les tâches qui peuvent être effectuée {tid=15 cdate=11/02/2009 ddate=11/02/2009} +Implémenter HttpStore\n\nCet objet dérive de SimpleStore.\n- GET du contenu du store et écriture dans un fichier cache local\n- Opérations éventuelles sur le fichier\n- Si modifications sur le fichier local, envoyer les modifications sur le store\ndistant\n- Si le store distant n'est pas disponible, travailler sur le cache local\n\nLe store distant doit supporter les opérations suivantes:\n- GET http://..../nom_du_store --> récupérer tout le store\n- GET http://..../nom_du_store?header --> récupérer l'en-tête du store\n- POST http://..../nom_du_store --> modifier le store distant {tid=16 cdate=11/02/2009} +Projet par défaut pour un store\n\nSi un store se trouve dans un répertoire de projet, il "hérite" de ce projet\ndans sa liste projects. Cette information n'est pas inscrite, elle est\ndynamique.\n\nDe plus, quand on se trouve dans un répertoire de projet, ajouter en premier,\ns'il existe, le fichier Tasks.txt ou wiki/Tasks.txt (configurable un jour avec\n.utools/tasksrc) à la liste des stores. Ainsi, l'ajout se fera dans le store du\nprojet. Il est important de faire cela uniquement si le fichier existe, pour ne\npas créer intempestivement des fichiers dans un répertoire de projet.\n\npratiquement, faire un module utools.tasks.projects et une classe Project avec\nles méthodes:\n- getprojdir()\n- gettasksfile()\n- getpurgedfile()\n- ... {tid=17 cdate=12/02/2009} +'tasks.py -g' devient 'tasks.py gui' qui est équivalent à 'tasks.py list' et\nprend les mêmes options, mais affiche les tâches sous forme graphique plutôt\nque sur la console {tid=18 cdate=13/02/2009 ddate=13/02/2009} +Rendre le filtre obligatoire pour les opérations edit, do, remove {tid=19 cdate=13/02/2009 ddate=13/02/2009} +Faire un générateur de sources\n\nL'idée est de pouvoir faire un générateur non plus avec des templates, mais avec\nun programme, comme par exemple:\n\nc = JavaGenerator(srcdir=..., file=...)\n// srcdir permet de savoir automatiquement le nom et le chemin du fichier en\n// fonction de son package\nc.startclass("my.package.MyClass")\nc.member("x", INT[, PRIVATE])\nc.startmethod(Type(INT), "getX")\nc.returns(Member("x"))\nc.end()\nc.startmethod(Type(VOID), "setX", Arg("x", INT))\nc.assignment(Member("x"), Arg("x"))\nc.end()\nc.end()\n\nIl est possible aussi d'avoir des raccourcis:\n\nc.property("x", INT)\n// équivalent aux définitions ci-dessus\n\nLes classes sont indiquées pleinement qualifiées, et sont importées au besoin. {tid=20 cdate=19/02/2009} +lors de la purge, il faut écrire toutes les informations dérivées (projets, contextes, etc.) pour ne perdre aucune information. {tid=21 cdate=05/04/2009} +Si on trouve dans un répertoire de projet, mettre Tasks.txt de ce projet en premier dans la liste des stores\n\nLa création se fait alors par défaut dans ce projet.\nAttention. ne faire cela que si le fichier existe. {tid=22 cdate=05/04/2009} +te 0.17 trouve deux tâches à éditer, dont l'une est None3???\n\nDans edit_file(), on ne tenait pas compte du fait que setrow peut être vide. {tid=23 cdate=07/06/2009 ddate=07/06/2009} +Ajouter le suivi du temps pour les tâches\n\nLe format pourrait être des lignes de la forme:\n\n@25/06/2009: 9h00-10h00, 11h23-11h45, 13h30-17h00\n@24/06/2009: 9h00-12h00, 13h30-\n\nen l'occurence avec les lignes précédentes, la tâche est toujours en cours.\nLe temps total passé sur la tâche, ainsi que peut-être certaines statistiques, permet de savoir où passe son temps.\n\nCe type de suivi pourrait n'être valable que pour les tâches standard et planifiées.\nIl faudrait probablement associer aux contextes des horaires, pour éviter de compter le temps de façon intempestive: par exemple, si je commence une tâche ce matin, et que j'oublie d'arrêter le compteur jusqu'à demain, seul le temps ouvrable est compté. {tid=24 cdate=25/06/2009 ddate=23/07/2009} +Ajouter des horaires aux contextes\n\nIl faudrait pouvoir borner la zone d'influence d'un contexte. on pourrait avoir:\n\n@{8h30-12h30,13h30-17h00} pour indiquer que par défaut, les tâches sans contexte s'effectuent dans les heures ouvrables.\n@perso{12h30-13h30,17h00-8h30,w6-7} pour indiquer que le contexte perso est valable le week-end et les heures non ouvrables\n\non peut aussi imaginer inscrire les dates des vacances dans le contexte @perso\n\nensuite, quand on filtre sur les contextes, on n'affiche que les tâches qui peuvent être effectuées dans la plage horaire en cours. {tid=25 cdate=25/06/2009} +si l'id d'un élément contient un point '.', jQuery.tooltip ne fonctionne pas {tid=26 cdate=17/07/2009 ddate=21/07/2009} +passer à la ligne dans les tooltips {tid=27 cdate=17/07/2009 ddate=23/07/2009} +pour l'affichage, est-il possible d'afficher alternativement les lignes avec une fond blanc et un fond gris, pour une meilleur lecture? {tid=28 cdate=17/07/2009 ddate=23/07/2009} +les tags des tâches ne doivent pas apparaitre à l'impression {tid=29 cdate=20/07/2009 ddate=23/07/2009} +faire le cache de hts au démarrage. ne le mettre à jour que si le store sur disque est modifié {tid=30 cdate=21/07/2009 ddate=21/07/2009} +supprimer les dates périmées du champ dates quand elles sont passées, si date_ref=now {tid=31 cdate=28/07/2009 ddate=11/08/2009} +possibilité de mixer !wN et +N pour dates {tid=32 cdate=29/07/2009 ddate=11/08/2009} +revert() si erreur lors d'une opération dans taskshttpd (peut-être comme load(), mais uniquement s'il y a eu des modifications) {tid=33 cdate=08/08/2009 ddate=08/08/2009} +/action fonctionne quel que soit le bouton Search/Do/Nodo {tid=34 cdate=08/08/2009 ddate=08/08/2009} +ajouter une action /edit pour modifier une tâche {tid=35 cdate=08/08/2009 ddate=25/08/2009} +ajouter l'action 'plan [-d date]' pour tasks, qui permet de planifier une tâche pour la date du jour ou celle spécifiée. {tid=36 cdate=08/08/2009 ddate=08/08/2009} +une tâche non planifiée faite aujourd'hui doit être affichée aujourd'hui {tid=37 cdate=08/08/2009 ddate=08/08/2009} +ajouter le support ajax de drap'n'drop pour taskshttpd, pour plannifier une tâche à une certaine date {tid=38 cdate=08/08/2009} +ajouter /plan à taskshttpd {tid=39 cdate=12/08/2009 ddate=12/08/2009} +toutes les tâches faites sont affichage aujourd'hui dans cal_future() {tid=40 cdate=14/08/2009 ddate=17/08/2009} +il semble que taskshttpd écrit les fichiers de store même sans besoin {tid=41 cdate=20/08/2009 ddate=20/08/2009} +afficher un signe si une tache est démarrée {tid=42 cdate=20/08/2009 ddate=25/08/2009} +Dans la recherche, ajouter automatiquement (?i) si ce n'est pas encore fait {tid=43 cdate=20/08/2009 ddate=21/08/2009} +mettre à jour le texte explicatif de tasks.output() {tid=44 cdate=21/08/2009 ddate=25/08/2009} +donner la possibilité d'échapper les tag=value ou @context ou +project dans un titre\n\npour forcer l'échappement, utiliser ~:\n\n tasks.py add ~date=15 ~@pas_un_context\n\nsinon, tag=value n'est reconnu que pour les propriétés valides {tid=45 cdate=21/08/2009 ddate=25/08/2009} +is_cal_future(0) doit tenir compte de get_hidden_done_delay() pour l'affichage\ndes tâches effectuées\n\nsynchroniser is_cal_future() avec le code de is_showable(): toutes les tâches\nfaites sont showable (wrt get_hidden_done_delay()) et doivent être affichées le\njour même.\n\n--> en fait, is_cal_future() et is_showable() sont différents: on filtre le\n résultat de la recherche avec is_showable(), ensuite is_cal_future() doit\n forcément traiter tous les cas pour que soient affichés toutes les tâches\n sélectionnées. {tid=46 cdate=21/08/2009 ddate=28/08/2009} +remplacer les icons par le set 'clock' {tid=47 cdate=23/08/2009 ddate=23/08/2009} +si un problème, dans /action, erreur sur le type de self qui doit être page et ne l'est pas\n\n--> le problème était dû à #58 {tid=48 cdate=26/08/2009 ddate=28/08/2009} +si erreur dans index (par exemple dans get_args), catcher l'erreur {tid=49 cdate=26/08/2009 ddate=26/08/2009} +ne pas rafraichir si une case a été cochée {tid=50 cdate=26/08/2009 ddate=26/08/2009} +sanitiser les arguments de winput(): supprimer les valeurs \\r {tid=51 cdate=26/08/2009 ddate=26/08/2009} +ajouter un message pour les opérations (supprimer, ajouter, ...) {tid=52 cdate=26/08/2009 ddate=28/08/2009} +migrer vers une utilisation systématique de propriétés, pour éviter la dualité {get_x, x}, sauf peut-être si get_x() ou set_x() prennent des arguments supplémentaires {tid=53 cdate=26/08/2009} +méthode pour spécifier la version de python requise pour un module {tid=54 cdate=26/08/2009} +ajouter un argument until à Lines.readlines() pour lire jusqu'à ce qu'une fonction retourne True {tid=55 cdate=26/08/2009 ddate=26/08/2009} +enregistrer et donner accès à tous les précédentes requêtes\n\nun bouton à côté de query liste tous les précédentes termes {tid=56 cdate=27/08/2009 ddate=28/08/2009} +possibilité de choisir vers quel store l'écriture est faite {tid=57 cdate=27/08/2009 ddate=28/08/2009} +supprimer l'import circulaire sur itfctl {tid=58 cdate=28/08/2009 ddate=28/08/2009} +réorganiser les imports\n\nmettre les valeurs __all__ tout en haut du fichier, avant les imports {tid=59 cdate=02/09/2009 ddate=21/09/2009} +tasks edit force l'encoding du fichier en utf-8 {tid=60 cdate=02/09/2009} +double-cliquer sur une tâche permet de l'éditer + ajouter un bouton cancel {tid=61 cdate=06/09/2009 ddate=06/09/2009} +régler la taille du textarea pour l'édition en fonction du nombre de lignes {tid=62 cdate=06/09/2009 ddate=06/09/2009} +supprimer le strip automatique de nl pour appendtext {tid=63 cdate=13/09/2009 ddate=13/09/2009} +modifier les templates pour ajouter nl explicitement ou non {tid=64 cdate=13/09/2009 ddate=13/09/2009} +ajouter bp ou blueprint dans HtmlTempl {tid=65 cdate=13/09/2009 ddate=13/09/2009} +renommer use_utools en utools {tid=66 cdate=13/09/2009 ddate=13/09/2009} +supprimer le gap entre les messages {tid=67 cdate=03/10/2009} +modifier web.template pour supprimer les espaces de début de ligne\n\npar exemple:\n\n cond:\n \n\ndonne\n\n \n {tid=68 cdate=10/11/2009} +ajouter dans web.pages le support des listes (vérifier que ce n'est pas None qui est ressorti) {tid=69 cdate=05/12/2009} +Date devient immutable par défaut (toutes les opérations retournent une copie), mais une méthode permet de le rendre mutable {tid=70 cdate=06/12/2009} diff --git a/pyulib/devel/doctests b/pyulib/devel/doctests new file mode 100755 index 0000000..99452a2 --- /dev/null +++ b/pyulib/devel/doctests @@ -0,0 +1,29 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +scriptdir="$(dirname "$0")" +source "./python_interpreters" +eval "$("$scriptdir/spp" -P)" + +pythonver=python +args=() +if [ "$1" == "-v" ]; then + args=("${args[@]}" -v) + shift +elif [[ "$1" =~ -[0-9]+ ]]; then + pythonver="python${1#-}" + shift +fi +pythonver="${!pythonver}" +if [ -z "$pythonver" ]; then + echo "Impossible de trouver la version de python" + exit 1 +fi + +# initialiser la liste par défaut des modules si l'utilisateur ne donne pas de +# module à tester +[ -n "$*" ] || set ulib.base.config +for module in "$@"; do + echo ">>> $1" + exec "$pythonver" -m "$1" "${args[@]}" +done diff --git a/pyulib/devel/idleshell b/pyulib/devel/idleshell new file mode 100755 index 0000000..e448cae --- /dev/null +++ b/pyulib/devel/idleshell @@ -0,0 +1,10 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +scriptdir="$(dirname "$0")" +source "./python_interpreters" +eval "$("$scriptdir/spp" -P)" + +echo ">>> Shell Python pour pyulib" +idlever="idle$1" +exec "${!idlever}" -i -c "$(<"$scriptdir/shell_init")" diff --git a/pyulib/devel/mkdiff-webpy.sh b/pyulib/devel/mkdiff-webpy.sh new file mode 100755 index 0000000..3ea0957 --- /dev/null +++ b/pyulib/devel/mkdiff-webpy.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +scriptdir="$(cd "$(dirname "$0")"; pwd)" + +webpy="${1:-$scriptdir/web.py-0.33/web}" +if [ -d "$webpy" ]; then + webpy="$(cd "$webpy"; pwd)" + webpy="${webpy#$scriptdir/}" +else + echo "$webpy: repertoire introuvable" +fi + +cd "$scriptdir" +diff -ur -x .svn -x "*.pyc" "$webpy" "../src/ulib/ext/web" diff --git a/pyulib/devel/pt b/pyulib/devel/pt new file mode 100755 index 0000000..d59a922 --- /dev/null +++ b/pyulib/devel/pt @@ -0,0 +1,6 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +scriptdir="$(dirname "$0")" + +exec Tasks -s "$scriptdir/Tasks.txt" "$@" diff --git a/pyulib/devel/pyshell b/pyulib/devel/pyshell new file mode 100755 index 0000000..a3aad2e --- /dev/null +++ b/pyulib/devel/pyshell @@ -0,0 +1,10 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +scriptdir="$(dirname "$0")" +source "./python_interpreters" +eval "$("$scriptdir/spp" -P)" + +echo ">>> Shell Python pour pyulib" +pythonver="python$1" +exec "${!pythonver}" -i -c "$(<"$scriptdir/shell_init")" diff --git a/pyulib/devel/python_interpreters b/pyulib/devel/python_interpreters new file mode 100644 index 0000000..0975ac0 --- /dev/null +++ b/pyulib/devel/python_interpreters @@ -0,0 +1,29 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +PYTHON= +PYTHON23="$HOME/opt/python2.3/bin/python" +PYTHON24= +PYTHON25= +PYTHON26= + +################################################################################ +# Partie non configurable + +python="$(type -P "${PYTHON:-python}")" +python23="$(type -P "${PYTHON23:-$(which python2.3 2>/dev/null)}")" +python24="$(type -P "${PYTHON24:-$(which python2.4 2>/dev/null)}")" +python25="$(type -P "${PYTHON25:-$(which python2.5 2>/dev/null)}")" +python26="$(type -P "${PYTHON26:-$(which python2.6 2>/dev/null)}")" + +idle="$(type -P "${IDLE:-idle}")" +idle23="$(type -P "${IDLE23:-$(which idle2.3 2>/dev/null)}")" +idle24="$(type -P "${IDLE24:-$(which idle2.4 2>/dev/null)}")" +idle25="$(type -P "${IDLE25:-$(which idle2.5 2>/dev/null)}")" +idle26="$(type -P "${IDLE26:-$(which idle2.6 2>/dev/null)}")" + +for i in "" 23 24 25 26; do + v="python$i" + [ -x "${!v}" ] || eval "$v=" + v="idle$i" + [ -x "${!v}" ] || eval "$v=" +done diff --git a/pyulib/devel/shell_init b/pyulib/devel/shell_init new file mode 100644 index 0000000..78c70d4 --- /dev/null +++ b/pyulib/devel/shell_init @@ -0,0 +1,58 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +import pdb +from ulib.all import * + +############################################################################### +#for first in ('1/12/9', '1/1/10', '1/2/10', '1/3/10', '1/4/10', '1/5/10', '1/6/10', '1/7/10'): +# date = parse_date(first) +# print "Mois de %s" % date.format("%fm") +# for debut, fin in date.get_monthweeks(False): +# print " %s - %s" % (debut, fin) + +############################################################################### +##from ulib.tasks import * +##ts = SimpleStore('/home/jclain/Tasks.txt') +##t1 = ts.tasks[0:1] and ts.tasks[0] or None +##t2 = ts.tasks[1:2] and ts.tasks[1] or None +# +#from ulib.web import * +#P = web.template.Parser +#parser1=P("""$for item in list: +# $for jtem in ljst: +# hello world $item, $jtem\n +#""") +#p1=parser1.parse +#parser2=P("""$for item in list: +# indented_4spc +#\tindented_tabs +#\t$for jtem in ljst: +#\t\thello world $item, $jtem +#\t\t\tindented_tabs +#\t\t indented_4spc +#\t\tnot_indented +#""") +#p2=parser2.parse + +############################################################################### +#from ulib.p.uinc import FileSpec, FileSpecs + +############################################################################### +#from ulib.p.templ import * + +############################################################################### +#from ulib.base.args import Options + +#options=Options() +#options.add_option(('help','h'), Options.SetValue(True)) +#options.add_option(('no-force', 'n'), Options.SetValue(False, initial=True)) + +############################################################################### +from ulib.p.templ.base import Templ +from ulib.p.templ.shtempl import ShTempl + +d = ShTempl('default.sh') +x = ShTempl('exec.sh', ['-x']) +n = ShTempl('noexec.sh', ['-n']) + + diff --git a/pyulib/devel/spp b/pyulib/devel/spp new file mode 100755 index 0000000..74d958e --- /dev/null +++ b/pyulib/devel/spp @@ -0,0 +1,59 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +source "$(dirname "$0")/../../ulib/ulib" && urequire DEFAULTS || exit 1 +pyulibdir="$(abspath "$scriptdir/..")" +utoolsdir="$(abspath "$scriptdir/../..")" + +new_pc= +show_pc=1 +show_path= +show_ppath= +default=1 +while [ -n "$1" ]; do + [ "$1" == "-n" ] && default= && show_pc= + [ "$1" == "-p" ] && default= && show_path=1 + [ "$1" == "-P" ] && default= && show_ppath=1 + shift +done +[ -n "$default" ] && show_pc=1 && show_path=1 && show_ppath=1 + +function quote_pc() { + local s + s="${1//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//$/\\$}" + s="${s//then +/then }" + s="${s// +/; }" + echo "$s" +} + +srcdir="$pyulibdir/src" +testdir="$pyulibdir/test" + +pc_prefix="echo /SPP/" +if [ "${PROMPT_COMMAND#$pc_prefix}" == "$PROMPT_COMMAND" ]; then + new_pc="$pc_prefix; $PROMPT_COMMAND" +fi + +array_from_path path "$PATH" +array_del path /usr/local/utools +array_del path /usr/local/nutools +array_del path "$utoolsdir" +array_ins path "$srcdir/uapps" +array_ins path "$utoolsdir" + +array_from_path ppath "$PYTHONPATH" +array_del ppath /usr/local/utools +array_del ppath /usr/local/nutools +array_del ppath "$srcdir" +array_del ppath "$testdir" +array_ins ppath "$testdir" +array_ins ppath "$srcdir" + +[ -n "$show_pc" -a -n "$new_pc" ] && echo "export PROMPT_COMMAND=\"$(quote_pc "$new_pc")\"" +IFS=: +[ -n "$show_path" ] && echo "export PATH=\"${path[*]}\"" +[ -n "$show_ppath" ] && echo "export PYTHONPATH=\"${ppath[*]}\"" diff --git a/pyulib/devel/web.py-0.33.patch b/pyulib/devel/web.py-0.33.patch new file mode 100644 index 0000000..307554f --- /dev/null +++ b/pyulib/devel/web.py-0.33.patch @@ -0,0 +1,165 @@ +diff -ur -x .svn -x '*.pyc' web.py-0.33/web/application.py ../src/ulib/ext/web/application.py +--- web.py-0.33/web/application.py 2009-10-28 09:45:10.000000000 +0400 ++++ ../src/ulib/ext/web/application.py 2009-11-18 08:54:43.000000000 +0400 +@@ -352,7 +352,7 @@ + ctx.realhome = ctx.home + ctx.ip = env.get('REMOTE_ADDR') + ctx.method = env.get('REQUEST_METHOD') +- ctx.path = env.get('PATH_INFO') ++ ctx.path = env.get('PATH_INFO') or '' + # http://trac.lighttpd.net/trac/ticket/406 requires: + if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'): + ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], ctx.homepath) +@@ -376,6 +376,15 @@ + + ctx.app_stack = [] + ++ _handler_configurator = None ++ ++ def set_handler_configurator(self, handler_configurator): ++ self._handler_configurator = handler_configurator ++ ++ def configure_handler(self, handler): ++ if self._handler_configurator is not None: ++ self._handler_configurator(handler) ++ + def _delegate(self, f, fvars, args=[]): + def handle_class(cls): + meth = web.ctx.method +@@ -383,7 +392,9 @@ + meth = 'GET' + if not hasattr(cls, meth): + raise web.nomethod(cls) +- tocall = getattr(cls(), meth) ++ handler = cls() ++ self.configure_handler(handler) ++ tocall = getattr(handler, meth) + return tocall(*args) + + def is_class(o): return isinstance(o, (types.ClassType, type)) +diff -ur -x .svn -x '*.pyc' web.py-0.33/web/httpserver.py ../src/ulib/ext/web/httpserver.py +--- web.py-0.33/web/httpserver.py 2009-10-28 09:45:10.000000000 +0400 ++++ ../src/ulib/ext/web/httpserver.py 2009-11-18 08:54:44.000000000 +0400 +@@ -1,6 +1,8 @@ + __all__ = ["runsimple"] + + import sys, os ++from os import path ++import urlparse, posixpath, urllib + from SimpleHTTPServer import SimpleHTTPRequestHandler + + import webapi as web +@@ -126,7 +128,7 @@ + self.app = func + self.serverShuttingDown = 0 + +- print "http://%s:%d/" % server_address ++ #print "http://%s:%d/" % server_address + WSGIServer(func, server_address).serve_forever() + + def runsimple(func, server_address=("0.0.0.0", 8080)): +@@ -141,7 +143,7 @@ + + server = WSGIServer(server_address, func) + +- print "http://%s:%d/" % server_address ++ #print "http://%s:%d/" % server_address + try: + server.start() + except KeyboardInterrupt: +@@ -161,6 +163,19 @@ + self.environ = environ + self.start_response = start_response + ++ def translate_path(self, path): ++ path = urlparse.urlparse(path)[2] ++ path = posixpath.normpath(urllib.unquote(path)) ++ words = path.split('/') ++ words = filter(None, words) ++ path = web.config.get('BASEDIR', os.getcwd()) ++ for word in words: ++ _, word = os.path.splitdrive(word) ++ _, word = os.path.split(word) ++ if word in (os.curdir, os.pardir): continue ++ path = os.path.join(path, word) ++ return path ++ + def send_response(self, status, msg=""): + self.status = str(status) + " " + msg + +diff -ur -x .svn -x '*.pyc' web.py-0.33/web/__init__.py ../src/ulib/ext/web/__init__.py +--- web.py-0.33/web/__init__.py 2009-10-28 09:45:10.000000000 +0400 ++++ ../src/ulib/ext/web/__init__.py 2009-11-18 08:54:43.000000000 +0400 +@@ -26,7 +26,7 @@ + from debugerror import * + from application import * + from browser import * +-import test ++#import test + try: + import webopenid as openid + except ImportError: +diff -ur -x .svn -x '*.pyc' web.py-0.33/web/template.py ../src/ulib/ext/web/template.py +--- web.py-0.33/web/template.py 2009-10-28 09:45:10.000000000 +0400 ++++ ../src/ulib/ext/web/template.py 2009-11-18 10:00:17.314384800 +0400 +@@ -382,6 +382,21 @@ + readline = iter([text]).next + tokens = tokenize.generate_tokens(readline) + return [t[1] for t in tokens] ++ ++ def tabsout(self, line, indent): ++ indent = indent.replace('\t', ' ') ++ re_tabs = re_compile(r'^\t+') ++ mo = re_tabs.match(line) ++ if mo is None: ++ return line, 0 ++ else: ++ actual_nbtabs = len(mo.group(0)) ++ nbtabs = max(0, actual_nbtabs - len(indent) / 4) ++ return re_compile(r'\t').sub(' ', line, actual_nbtabs), nbtabs ++ ++ def tabsin(self, line, nbtabs): ++ if nbtabs > 0: line = re_compile(r' ').sub('\t', line, nbtabs) ++ return line + + def read_indented_block(self, text, indent): + r"""Read a block of text. A block is what typically follows a for or it statement. +@@ -400,11 +415,13 @@ + + block = "" + while text: +- line, text2 = splitline(text) ++ oline, text2 = splitline(text) ++ line, nbtabs = self.tabsout(oline, indent) + if line.strip() == "": + block += '\n' + elif line.startswith(indent): +- block += line[len(indent):] ++ line = line[len(indent):] ++ block += self.tabsin(line, nbtabs) + else: + break + text = text2 +@@ -446,10 +463,12 @@ + return first_indent or "" + + # find the indentation of the block by looking at the first line ++ text, nbtabs = self.tabsout(text, begin_indent) + first_indent = find_indent(text)[len(begin_indent):] + indent = begin_indent + min(first_indent, INDENT) + + block, text = self.read_indented_block(text, indent) ++ text = self.tabsin(text, nbtabs) + + return self.create_block_node(keyword, stmt, block, begin_indent), text + +@@ -520,7 +539,7 @@ + return self.defwith + self.suite.emit(indent + INDENT) + + def __repr__(self): +- return "" % (self.defwith, self.nodes) ++ return "" % (self.defwith, self.suite) + + class TextNode: + def __init__(self, value): +Seulement dans ../src/ulib/ext/web/wsgiserver: LICENSE.txt diff --git a/pyulib/migrate/base.py b/pyulib/migrate/base.py new file mode 100644 index 0000000..4c8e7c5 --- /dev/null +++ b/pyulib/migrate/base.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__ebegin_params = {} +def ebegin(msg, level=None, params=__ebegin_params): + if not check_verbosity(level, V_INFO): return + params['status'] = 0 + params['first'] = True + print_stdout('%s:' % msg, '* ', COLOR_WHITE, nl=None) + +def edot(status=0, msg=None, warn=None, level=None, params=__ebegin_params): + # note: status est un exitcode (logique inversée) + if not check_verbosity(level, V_INFO): return + if not status and warn is not None: + status = 1 + if not params.get('status', 0): params['status'] = status + if params.get('first', True): + print_stdout(' ', nl=None) + params['first'] = False + if display_debug(): + if status: + if warn is not None: + print_stdout(warn and '(%s)' % warn or '', 'w', COLOR_YELLOW, before=warn and '\n ' or '', nl=None) + else: + print_stdout(msg and '(%s)' % msg or '', 'x', COLOR_RED, before=msg and '\n ' or '', nl=None) + else: + print_stdout(msg and '(%s)' % msg or '', '.', before=msg and '\n ' or '', nl=None) + else: + if status: + if warn is not None: + print_stdout('', 'w', COLOR_YELLOW, nl=None) + else: + print_stdout('', 'x', COLOR_RED, nl=None) + else: + print_stdout('.', nl=None) + +def eend(status=None, level=None, params=__ebegin_params): + # note: status est un exitcode (logique inversée) + if not check_verbosity(level, V_INFO): return + if status is None: status = params.get('status', 0) + if not status: + print_stdout('', ' ok', COLOR_GREEN) + else: + print_stdout('', ' error', COLOR_RED) + return status diff --git a/pyulib/migrate/tasks1/TODO.py b/pyulib/migrate/tasks1/TODO.py new file mode 100644 index 0000000..7d0568b --- /dev/null +++ b/pyulib/migrate/tasks1/TODO.py @@ -0,0 +1,745 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = ["TODO", "TODOFile", "TiddlyTODOFile", "TODOs"] + +try: True, False +except NameError: True, False = 1, 0 + +import os, re, types +from os import path + +from base import scriptdir +from dates import * +from files import TextFile +from config import ShConfigFile +from TiddlyWiki import * + +_marker = [] + +def is_seq(value): + return type(value) is types.ListType or type(value) is types.TupleType + +def quote_nl(text): + if text is not None: + text = text.replace("\\", r"\\") + text = text.replace("\n", r"\n") + return text + +def unquote_nl(text): + if text is not None: + text = text.replace(r"\n", "\n") + text = text.replace(r"\\", "\\") + return text + +class TODO: + def __init__(self, title=None, desc=None, contexts=None, projects=None, tags=None, todonum=None, todoline=None): + self.todonum = todonum + self.title = title + self.desc = desc + if title is not None: + self.text = quote_nl(title + (desc and "\n" + desc or "")) + self.contexts = contexts or () + self.projects = projects or () + self.tags = tags or {} + if todoline is not None: self.parse(todoline) + + RE_CONTEXT = re.compile(r'@([^ ]+)\s+') + RE_PROJECT = re.compile(r'\+([^ ]+)\s+') + RE_TAG = re.compile(r'([^= ]+)=([^ ]+)\s+') + + def parse(self, line): + contexts = [] + projects = [] + tags = {} + title = '' + desc = None + + while True: + mo = self.RE_CONTEXT.match(line) + if mo is not None: + contexts.append(mo.group(1)) + line = line[mo.end(0):] + continue + mo = self.RE_PROJECT.match(line) + if mo is not None: + projects.append(mo.group(1)) + line = line[mo.end(0):] + continue + mo = self.RE_TAG.match(line) + if mo is not None: + tags[mo.group(1)] = mo.group(2) + line = line[mo.end(0):] + continue + break + + text = line + pos = text.find("\\n") + if pos == -1: + title = text + desc = None + else: + title = text[:pos] + desc = text[pos+2:] + + self.text = text + self.title = unquote_nl(title) + self.desc = unquote_nl(desc) + self.contexts = contexts + self.projects = projects + self.tags = tags + + def to_line(self): + if self.title is None: return None + + tags = [] + if self.contexts is not None: tags.extend(map(lambda c: "@" + c, self.contexts)) + if self.projects is not None: tags.extend(map(lambda p: "+" + p, self.projects)) + if self.tags is not None: tags.extend(map(lambda (k, v): "%s=%s" % (k, v), self.tags.items())) + + line = "" + if tags: line = line + " ".join(tags) + " " + line = line + quote_nl(self.title) + if self.desc is not None: line = line + "\\n" + quote_nl(self.desc) + + return line + + def to_string(self): + id = self.tags.get("id", None) + if self.is_done(): string = "TODO[%s], fait le %s:" % (id, self.tags["done"]) + else: string = "TODO[%s]:" % id + + tags = [] + if self.contexts is not None: tags.extend(map(lambda c: "@" + c, self.contexts)) + if self.projects is not None: tags.extend(map(lambda p: "+" + p, self.projects)) + for tag in self.tags: + if tag != "id" and tag != "done" and not tag.endswith("_purged"): + tags.append("%s=%s" % (tag, self.tags[tag])) + if tags: string = string + " " + " ".join(tags) + string = string + "\n" + + string = string + self.title + if self.desc: string = string + "\n\n" + self.desc + + return string + + def __repr__(self): + return "TODO(%s)" % self.title + + # Accès aux tags + def __len__(self): + return len(self.tags) + def __getitem__(self, key, default=_marker): + if default is _marker: return self.tags[key] + else: return self.tags.get(key, default) + get = __getitem__ + def __setitem__(self, key, value): + self.tags[key] = value + def __delitem__(self, key): + del self.tags[key] + def __contains__(self, key): + return key in self.tags + def has_key(self, key): + return self.tags.has_key(key) + def keys(self): + return self.tags.keys() + def values(self): + return self.tags.values() + def items(self): + return self.tags.items() + + def is_done(self): + return self.has_key("done") + def is_project_purged(self, project): + return self.has_key("%s_purged" % project) + def set_project_purged(self, project, date=None): + if date is None: date = datef() + self.tags["%s_purged" % project] = date + + def has_projects(self): + return len(self.projects) != 0 + def is_all_purged(self): + for project in self.projects: + if not self.is_project_purged(project): return False + return True + + def is_project_released(self, project): + return self.has_key("%s_released" % project) + def set_project_released(self, project, date=None): + if date is None: date = datef() + self.tags["%s_released" % project] = date + + + # Filtre + def filter(self, filters=None, **kw): + if filters is None: filters = kw + else: filters.update(kw) + for filter, value in filters.items(): + if filter == "project": + if is_seq(value): + projects = value + for project in projects: + if project not in self.projects: return False + else: + project = value + if project not in self.projects: return False + elif filter == "context": + if is_seq(value): + contexts = value + for context in contexts: + if context not in self.contexts: return False + else: + context = value + if context not in self.contexts: return False + elif filter == "todonum": + if self.todonum != value: return False + else: + if value is None: + if filter in self.tags: return False + elif value == '*': + if filter not in self.tags: return False + else: + if filter not in self.tags or self.tags[filter] != value: return False + return True + +class TODOFile(TextFile): + def __init__(self, file, DONE_file=False, raise_exception=False): + TextFile.__init__(self) + + TODO_txt = DONE_file and 'DONE.txt' or 'TODO.txt' + todo_txt = DONE_file and 'done.txt' or 'todo.txt' + if path.isdir(file): + # on a donné le nom d'un répertoire. y chercher un fichier nommé + # TODO.txt ou todo.txt + dir = file + todofile = path.join(dir, TODO_txt) + if not path.exists(todofile): + todofile2 = path.join(dir, todo_txt) + if path.exists(todofile2): todofile = todofile2 + else: + # on a donné un nom de fichier + todofile = file + self.load(todofile, raise_exception=raise_exception) + + def __update_todonum(self): + todonum = 1 + for todo in self.todos: + todo.todonum = todonum + todonum = todonum + 1 + + def readlines(self, raise_exception=True): + TextFile.readlines(self, raise_exception=raise_exception) + + self.todos = [] + for line in self.lines: + self.todos.append(TODO(todoline=line)) + self.__update_todonum() + + def writelines(self): + """Ecrire les TODOS et les DONEs dans le fichier + """ + lines = [] + for todo in self.todos: + lines.append(todo.to_line()) + + self.lines = lines + TextFile.writelines(self) + save = writelines + + def __len__(self): + return len(self.todos) + def __getitem__(self, index): + return self.todos[index] + + def list(self, filters=None, **kw): + if type(filters) is types.IntType: return [self.todos[filters]] + elif isinstance(filters, TODO): + todo = filters + if todo in self.todos: return [todo] + else: + id = todo.get("id", None) + if id is None: return [] + filters = {"id": id} + + if filters is None: filters = kw + else: filters.update(kw) + return filter(lambda t: t.filter(filters), self.todos) + + def get(self, filters=None, **kw): + todos = self.list(filters, **kw) + if len(todos) == 0: return None + elif len(todos) > 1: raise ValueError("Plusieurs tâches correspondent aux critères") + else: return todos[0] + + def remove(self, filters=None, **kw): + todos = self.list(filters, **kw) + for todo in todos: + self.todos.remove(todo) + self.__update_todonum() + + def put(self, todo): + if not isinstance(todo, TODO): todo = TODO(todoline=todo) + self.remove(todo) + self.todos.append(todo) + self.__update_todonum() + return todo + + def get_file(self): + return self.pf + + def can_release(self): + return False + + def release(self, released, release_date=None, version=None, changes=None): + pass + +class TiddlyTODOFile: + RE_DATE = re.compile(r'(\d\d)/(\d\d)/(\d\d\d\d)$') + RE_ID = re.compile(r'(\d\d)/(\d\d)/(\d\d\d\d)-(\d+)$') + + def __init__(self, file, project): + if path.isdir(file): + file = path.join(file, "TODO.html") + self.tw = TiddlyWiki(file) + self.project = project + + def get_tiddler(self, id): + mo = self.RE_ID.match(id) + if mo is not None: tiddler = u"TODO-%s%s%s-%s" % (mo.group(3), mo.group(2), mo.group(1), mo.group(4)) + else: tiddler = ensure_unicode(id) + return tiddler + + def get(self, id): + return tw[self.get_tiddler(id)] + + def remove(self, todo_or_id): + if isinstance(todo_or_id, TODO): + todo = todo_or_id + id = todo.get("id", None) + else: + id = todo_or_id + if id is None: raise ValueError("Il n'est possible de mettre à jour qu'un TODO avec un id") + + self.tw.remove(self.get_tiddler(id)) + + def put(self, todo): + id = todo.get("id", None) + if id is None: raise ValueError("Il n'est possible de mettre à jour qu'un TODO avec un id") + + tiddler = self.get_tiddler(id) + modifier = None + modified = None + + twtext = todo.text + twtags = map(lambda c: "@" + c, todo.contexts) + map(lambda p: "+" + p, todo.projects) + if "date" in todo.tags: + mo = self.RE_DATE.match(todo.tags["date"]) + if mo is not None: + twtags.append("TODO-%s%s%s" % (mo.group(3), mo.group(2), mo.group(1))) + else: + twtags.append("TODO-%s" % todo.tags["date"]) + if todo.is_done(): + donetag = todo.is_project_released(self.project) and "RELEASED" or "DONE" + twtags.append(donetag) + twtext = twtext + "\n\nFAIT le %s" % todo.tags["done"] + else: + twtags.append("TODO") + + self.tw.put(Tiddler(tiddler, twtext, " ".join(twtags), modifier, modified)) + + def save(self): + self.tw.save() + + def get_file(self): + return self.tw.twpf + + def can_release(self): + return True + + def release(self, released, release_date=None, version=None, changes=None): + release_name = datef("Release-%Y%m%d", release_date) + tiddler = release_name + count = 0 + for tiddler in self.tw: + t = self.tw[tiddler] + if t.tags_contains(release_name): + count = count + 1 + if count == 0: tiddler = release_name + else: tiddler = "%s-%i" % (release_name, count) + + twtext = u"release du %s\nversion: %s\n" % (datef(FR_DATEF, release_date), version) + if changes: + twtext = twtext + "\n" + ensure_unicode(changes) + if released: + twtext = twtext + "\n" + for done in released: + twtext = twtext + "* <>\n" % self.get_tiddler(done.get("id", None)) + twtags = [release_name, "release"] + + self.tw.put(Tiddler(tiddler, twtext, " ".join(twtags))) + +class TODOs: + DEFAULTS = {"scriptdir": scriptdir} + + def __init__(self, todorcfile=None): + if todorcfile is None: + todorcfile = path.join(os.environ["HOME"], ".utools/todorc") + if path.exists(todorcfile): + todorc = ShConfigFile(todorcfile, defaults=self.DEFAULTS) + else: + todorc = self.DEFAULTS + tododir = todorc.get("tododir", path.split(todorcfile)[0]) + todofile = todorc.get("todofile", path.join(tododir, "TODO.txt")) + donefile = todorc.get("donefile", path.join(tododir, "DONE.txt")) + projdirs = todorc.get("projdirs", "") + + self.tododir = tododir + self.todofile = todofile + self.donefile = donefile + self.projdirs = filter(None, projdirs.split(":")) + + self.todos = TODOFile(self.todofile) + self.dones = TODOFile(self.donefile) + + def get_projdir(self, project): + for projdir in self.projdirs: + dir = path.join(projdir, project) + if path.isdir(dir): + return dir + return None + + def __len__(self): + return len(self.todos) + def __getitem__(self, index): + return self.todos[index] + + def __str__(self): + lines = [] + for todo in self.todos: + lines.append("%i: %s" % (todo.todonum, todo.to_string())) + return "\n".join(lines) + + def p(self): + print self + + ## Interface utilisateur + def list(self, filters=None, **kw): + return self.todos.list(filters, **kw) + + def get(self, filters=None, **kw): + todos = self.list(filters, **kw) + if len(todos) == 0: return None + elif len(todos) > 1: raise ValueError("Plusieurs tâches correspondent aux critères") + else: return todos[0] + + def __put_in_projects(self, todo, DONE_file=False): + for project in todo.projects: + projdir = self.get_projdir(project) + if projdir is not None: + twhtml = path.join(projdir, "TODO.html") + if path.exists(twhtml): + todofile = TiddlyTODOFile(twhtml, project) + else: + if DONE_file: + todofile = TODOFile(projdir, DONE_file=False) + todofile.remove(todo) + todofile.save() + todofile = TODOFile(projdir, DONE_file=DONE_file) + todofile.put(todo) + todofile.save() + + def put(self, todo): + todo = self.todos.put(todo) + self.__put_in_projects(todo) + self.todos.save() + + def add(self, title=None, desc=None, contexts=None, projects=None, tags=None, set_done=False, todoline=None): + tags = tags or {} + # Calculer la date + date = datef() + if not tags.has_key("date"): tags["date"] = date + # Faut-il finir la tâche tout de suite? + if set_done and not tags.has_key("done"): + tags["done"] = date + # Calculer l'id + if not tags.has_key("id"): + count = 1 + for todo in self.todos: + if todo.has_key("id") and todo["id"].startswith(date + "-"): count = count + 1 + elif todo.has_key("date") and todo["date"] == date: count = count + 1 + for todo in self.dones: + if todo.has_key("id") and todo["id"].startswith(date + "-"): count = count + 1 + elif todo.has_key("date") and todo["date"] == date: count = count + 1 + tags["id"] = "%s-%i" % (date, count) + + if todoline is None: + todo = TODO(title, desc, contexts, projects, tags) + else: + todo = TODO(todoline=todoline) + todo.tags.update(tags) + + self.put(todo) + + def do(self, filters=None, **kw): + todos = self.list(filters, **kw) + date = datef() + modified = False + for todo in todos: + if not todo.is_done(): + todo["done"] = date + if todo.has_key("pri"): del todo["pri"] + modified = True + self.__put_in_projects(todo) + if modified: self.todos.save() + + def __remove_from_projects(self, todo): + for project in todo.projects: + projdir = self.get_projdir(project) + if projdir is not None: + twhtml = path.join(projdir, "TODO.html") + if path.exists(twhtml): todofile = TiddlyTODOFile(twhtml, project) + else: todofile = TODOFile(projdir) + todofile.remove(todo) + todofile.save() + + def remove(self, filters=None, **kw): + todos = self.list(filters, **kw) + modified = False + for todo in todos: + self.todos.remove(todo) + modified = True + self.__remove_from_projects(todo) + if modified: self.todos.save() + + def purge(self, project=None): + modified = False + if project is None: + # purger les tâches qui ne sont assignées à aucun projet + todos = self.todos.list(done='*') + dones = [] + for todo in todos: + if not todo.has_projects(): + dones.append(todo) + for done in dones: + self.dones.put(done) + self.todos.remove(done) + modified = True + self.__put_in_projects(done, DONE_file=True) + else: + # D'abord calculer les tâches qui sont elligibles à la purge + todos = self.todos.list(project=project, done='*') + # Puis les marquer comme purgées + dones = [] + purgeable = [] + for todo in todos: + if not todo.is_project_purged(project): + todo.set_project_purged(project) + modified = True + if todo.is_all_purged(): purgeable.append(todo) + else: dones.append(todo) + for done in dones: + self.__put_in_projects(done) + for done in purgeable: + self.dones.put(done) + self.todos.remove(done) + modified = True + self.__put_in_projects(done, DONE_file=True) + + if modified: + self.todos.save() + self.dones.save() + + def list_releaseable(self, project): + """Lister les tâches qui sont concernées par self.release() + """ + releaseable = [] + # chercher dans self.todos + purged = self.todos.list(project=project, done='*') + purged = filter(lambda t: t.is_project_purged(project), purged) + for todo in purged: + if not todo.is_project_released(project): + releaseable.append(todo) + # puis dans self.dones + purged = self.dones.list(project=project, done='*') + purged = filter(lambda t: t.is_project_purged(project), purged) + for todo in purged: + if not todo.is_project_released(project): + releaseable.append(todo) + return releaseable + + def mark_released(self, project): + """Marquer les tâches du projet comme released + Retourner le TODOFile ou TiddlyTODOFile qui correspond au projet + """ + #released = [] + # D'abord calculer les tâches qui sont elligibles à la release, puis les marquer comme released + # d'abord pour self.todos + purged = self.todos.list(project=project, done='*') + purged = filter(lambda t: t.is_project_purged(project), purged) + modified = False + for todo in purged: + if not todo.is_project_released(project): + todo.set_project_released(project) + modified = True + #released.append(todo) + # Mettre à jour les projets + self.__put_in_projects(todo) + if modified: self.todos.save() + # puis pour self.dones + purged = self.dones.list(project=project, done='*') + purged = filter(lambda t: t.is_project_purged(project), purged) + modified = False + for todo in purged: + if not todo.is_project_released(project): + todo.set_project_released(project) + modified = True + #released.append(todo) + # Mettre à jour les projets + self.__put_in_projects(todo, DONE_file=True) + if modified: self.dones.save() + + def get_todofile(self, project): + """Obtenir le TODOFile ou le TiddlyTODOFile correspondant au projet, ou + None si le projet est invalide + """ + projdir = self.get_projdir(project) + if projdir is not None: + twhtml = path.join(projdir, "TODO.html") + if path.exists(twhtml): todofile = TiddlyTODOFile(twhtml, project) + else: todofile = TODOFile(projdir) + return todofile + return None + + class Report: + # True si une tâche avec priorité a été rencontrée + pri = False + # Nom + name = None + # True si une tâche valide a été rencontrée + valid = False + # statistiques pour les tâches valides + valid_open = 0 + valid_closed = 0 + valid_total = 0 + valid_open_perc = 0 + valid_closed_perc = 0 + # statistiques pour les tâches purgées + purged_open = 0 + purged_closed = 0 + purged_total = 0 + purged_open_perc = 0 + purged_closed_perc = 0 + # statistiques pour toutes les tâches + open = 0 + closed = 0 + total = 0 + open_perc = 0 + closed_perc = 0 + + def __init__(self, name): + self.name = name + + def update_perc(self): + if self.valid_total > 0: + self.valid_open_perc = self.valid_open * 100 / self.valid_total + self.valid_closed_perc = self.valid_closed * 100 / self.valid_total + if self.purged_total > 0: + self.purged_open_perc = self.purged_open * 100 / self.purged_total + self.purged_closed_perc = self.purged_closed * 100 / self.purged_total + if self.total > 0: + self.open_perc = self.open * 100 / self.total + self.closed_perc = self.closed * 100 / self.total + def update_open(self, valid=True): + if valid: + self.valid_open = self.valid_open + 1 + self.valid_total = self.valid_total + 1 + else: + self.purged_open = self.purged_open + 1 + self.purged_total = self.purged_total + 1 + self.open = self.open + 1 + self.total = self.total + 1 + self.update_perc() + def update_closed(self, valid=True): + if valid: + self.valid_closed = self.valid_closed + 1 + self.valid_total = self.valid_total + 1 + else: + self.purged_closed = self.purged_closed + 1 + self.purged_total = self.purged_total + 1 + self.closed = self.closed + 1 + self.total = self.total + 1 + self.update_perc() + def update(self, todo, valid=False): + if todo.is_done(): self.update_closed(valid) + else: self.update_open(valid) + self.valid = self.valid or valid + + def valid_closed_bar(self, max=20): + n = self.valid_closed_perc * max / 100 + return "=" * n + (max - n) * " " + + def purged_closed_bar(self, max=20): + n = self.purged_closed_perc * max / 100 + return "=" * n + (max - n) * " " + + def closed_bar(self, max=20): + n = self.closed_perc * max / 100 + return "=" * n + (max - n) * " " + + def print_reports(self, title, reports): + if not reports: return + + print title + print "-" * len(title) + print + + def cmp_report(r0, r1): + r = cmp(r0.pri, r1.pri) + if r != 0: return r + r = -cmp(r0.valid_closed_perc, r1.valid_closed_perc) + if r != 0: return r + return cmp(r0.name, r1.name) + reports.sort(cmp_report) + + maxnamelen = reduce(max, map(lambda r: len(r.name), reports)) + for report in reports: + print "%s %3i%% [%s] %*s %2i / %i tâche(s)" % ( + report.pri and '*' or ' ', + report.valid_closed_perc, + report.valid_closed_bar(), + maxnamelen, report.name, + report.valid_closed, + report.valid_total + ) + print + + def do_report(self): + """Afficher un rapport sur les tâches + """ + projects = {} + contexts = {} + def update_stats(todo, valid, projects=projects, contexts=contexts): + for project in todo.projects: + report = projects.setdefault(project, self.Report("+%s" % project)) + report.update(todo, valid) + for context in todo.contexts: + report = contexts.setdefault(context, self.Report("@%s" % context)) + report.update(todo, valid) + for todo in self.todos: update_stats(todo, True) + for done in self.dones: update_stats(done, False) + + self.print_reports("Projets avec des tâches", filter(lambda p: p.valid, projects.values())) + self.print_reports("Contextes avec des tâches", filter(lambda p: p.valid, contexts.values())) + + purged = filter(lambda p: not p.valid, projects.values()) + if purged: + title = "Projets terminés (pas de tâches)" + print title + print "-" * len(title) + print + + maxnamelen = reduce(max, map(lambda r: len(r.name), purged)) + for report in purged: + print " %*s: %2i tâche(s)" % ( + maxnamelen, report.name, + report.purged_total + ) diff --git a/pyulib/migrate/tasks1/TODO_helper.py b/pyulib/migrate/tasks1/TODO_helper.py new file mode 100755 index 0000000..97b437b --- /dev/null +++ b/pyulib/migrate/tasks1/TODO_helper.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""%(name)s: Fonction à usage de TODO pour gérer les tiddlywikis +USAGE + %(name)s tiddlywiki.html [actions] +""" + +try: True, False +except NameError: True, False = 1, 0 + +import os, sys, re +from os import path +from time import localtime + +from base import * +from TiddlyWiki import * +from TiddlyWiki import ARGS_ENCODING # XXX +from TODO import * + +###################################################################### +# Fonctions de support pour TODO + +def do_update_todo(twfile, project=None, id=None, todoline=None, args=None): + """USAGE: update_todo project id todoline + """ + i = 0 + if project is None: + if args[i:i + 1]: project = args[i] + if project is None: die("Il faut spécifier le projet") + i = i + 1 + if id is None: + if args[i:i + 1]: id = args[i] + if project is None: die("Il faut spécifier l'id") + i = i + 1 + if todoline is None: + if args[i:i + 1]: todoline = unicode(args[i], ARGS_ENCODING) + if todoline is None: die("Il faut spécifier la ligne de TODO") + + todofile = TiddlyTODOFile(twfile, project) + if todoline: + todo = TODO(todoline=todoline) + todofile.put(todo) + else: + todofile.remove(id) + todofile.save() + +def do_report(todorc, **ignored): + """USAGE: report + """ + ts = TODOs(todorc) + ts.do_report() + +###################################################################### +# L'application principale + +def ensure(twfile): + if twfile is None: twfile = "TODO.html" + if not path.exists(twfile): die("Fichier inexistant: %s" % twfile) + else: return twfile + +def run(): + todorc = None + twfile = None + + opts, args = get_args(None, [ + ("h", "help", "Afficher l'aide"), + ("c:", None, "Spécifier le fichier todorc"), + ("f:", None, "Spécifier le fichier tiddlywiki.html"), + ]) + + for opt, value in opts: + if opt in ("-h", "--help"): + print __doc__ % {"name": path.split(sys.argv[0])} + sys.exit(0) + elif opt in ("-c", ): + todorc = value + elif opt in ("-f", ): + twfile = value + + action = args[0:1] and args[0] or "list" + if action in ("l", "list"): + list_tiddlers(ensure(twfile), args[1:]) + elif action in ('a', 'add', 'r', 'replace'): + replace_tiddler(ensure(twfile), args=args[1:]) + elif action in ('d', 'delete'): + delete_tiddler(ensure(twfile), args=args[1:]) + elif action in ('update_todo', 'update_done'): + do_update_todo(ensure(twfile), args=args[1:]) + elif action in ('report', ): + do_report(todorc, args=args[1:]) + elif action in ('update_all', ): + # XXX à faire: équivalent de TODO update + do_update_all(todorc, args=args[1:]) + else: + die("Action invalide: %s" % action) + +if __name__ == "__main__": + run() diff --git a/pyulib/migrate/tasks1/tiddlywiki.py b/pyulib/migrate/tasks1/tiddlywiki.py new file mode 100755 index 0000000..c57a8d7 --- /dev/null +++ b/pyulib/migrate/tasks1/tiddlywiki.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""%(name)s: Gérer un fichier html tiddlywiki +USAGE + %(name)s [-f tiddlywiki.html] action +""" + +try: True, False +except: True, False = 1, 0 + +import os, sys +from os import path +from pyutools.base import get_args, die +from pyutools.TiddlyWiki import * + +###################################################################### +# Programme principal + +def run(): + twfile = None + + opts, args = get_args(None, [ + ("h", "help", "Afficher l'aide"), + ("f:", None, "Spécifier le fichier tiddlywiki.html"), + ]) + + for opt, value in opts: + if opt in ("-h", "--help"): + print __doc__ % {"name": path.split(sys.argv[0])} + sys.exit(0) + elif opt in ("-f", ): + twfile = value + + if twfile is None: twfile = "TODO.html" + if not path.exists(twfile): die("Fichier inexistant: %s" % twfile) + + action = args[0:1] and args[0] or "list" + if action in ("l", "list"): + list_tiddlers(twfile, args[1:]) + elif action in ('a', 'add', 'r', 'replace'): + replace_tiddler(twfile, args=args[1:]) + elif action in ('d', 'delete'): + delete_tiddler(twfile, args=args[1:]) + else: + die("Action invalide: %s" % action) + +if __name__ == "__main__": + run() diff --git a/pyulib/migrate/tasks1/tiddlywiki2.py b/pyulib/migrate/tasks1/tiddlywiki2.py new file mode 100644 index 0000000..e6a1069 --- /dev/null +++ b/pyulib/migrate/tasks1/tiddlywiki2.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""%(name)s: Gestion d'un fichier tiddlywiki.html +USAGE + %(name)s [-f tiddlywiki.html] action [options] +""" + +try: True, False +except NameError: True, False = 1, 0 + +import re, types +from os import path +from time import localtime +from iso8859 import quote_entity, unquote_entity + +__all__ = ["ensure_unicode", "Tiddler", "TiddlyWiki", "list_tiddlers", "replace_tiddler", "delete_tiddler"] + +# XXX à configurer +ARGS_ENCODING="latin-1" + +###################################################################### +# Classes et constantes de base + +RE_TAG = re.compile(r'<([^ >]+)') +RE_ENDTAG = re.compile(r'') +RE_SOMETHING = re.compile(r'\S') +RE_ATTR1 = re.compile(r'([^= ]+)="([^"]*)"') +RE_ATTR2 = re.compile(r"([^= ]+)='([^']*)'") +def parseDiv(data): + attrs = {} + text = '' + + # Parser
") + data = data[1:] + # Texte + pos = data.find("<") + if pos == -1: raise ValueError("Impossible de trouver le tag de fin") + text = unquote_entity(data[:pos]) + data = data[pos:] + # Tag fermant + if data[:6] != '
': raise ValueError("Attendu
") + data = data[6:] + + return attrs, text, data + +def ensure_unicode(data): + if data is None: return None + elif type(data) is types.UnicodeType: return data + else: return unicode(data, ARGS_ENCODING) + +def quote_nl(text): + text = text.replace("\n", "\\n") + return text + +def unquote_nl(text): + text = text.replace("\\n", "\n") + return text + +def get_now(): + """Retourner la date actuelle dans le format attendu par le champ modified + """ + t = localtime() + return '%04i%02i%02i%02i%02i' % t[0:5] + +class Tiddler: + def __init__(self, tiddler=None, text=None, tags=None, modifier=None, modified=None, line=None): + self.tiddler = ensure_unicode(tiddler) + self.text = ensure_unicode(text) + self.tags = ensure_unicode(tags) + self.modifier = ensure_unicode(modifier) + self.modified = ensure_unicode(modified) + if line is not None: parse(line) + + def is_valid(self): + """Retourner True si ce Tiddler est valide + """ + return self.tiddler is not None + + def parse(self, line): + """Charger un Tiddler depuis line, et retourner le reste de la chaine qui n'a pas été parsé + """ + attrs, text, line = parseDiv(ensure_unicode(line)) + if text is not None: + self.tiddler = attrs["tiddler"] + self.modified = attrs["modified"] + self.modifier = attrs["modifier"] + self.tags = attrs["tags"] + self.text = unquote_nl(text) + return line + + def to_line(self): + """Retourner ce tiddler sous forme de ligne + """ + if not self.is_valid(): die("Il faut spécifier l'id du tiddler") + + line = u'' + line += quote_nl(quote_entity(self.text)) + line += u'
' + return line + + RE_SPACES = re.compile("\s+") + def tags_contains(self, tag): + return tag in self.RE_SPACES.split(self.tags) + +class TiddlyWiki: + def __init__(self, twfile=None): + self.twfile = None + self.prefix = None + self.suffix = None + self.tsmap = None + self.tslist = None + if twfile is not None: self.load(twfile) + + RE_MAGIC = re.compile(r'' + START_DIV = '
+ pos = pose + len(self.END_DIV) + else: + # end_save_area + break + pos = data.find(self.END_DIV, pos) + if pos == -1: + raise ValueError("%s: Impossible de trouver la fin de la zone de stockage" % self.file) + self.suffix = data[pos:] + data = data[:pos] + return True, data + except: + if raise_exception: raise + return False, None + + def load(self, file=None, raise_exception=True): + if file is not None: self.__update_file(file) + + self.valid = False + data = self.__load_prefix_and_suffix(self.pf, raise_exception)[1] + + # charger les données + self.valid = True + + self.byname = {} + self.tiddlers = [] + + data = self.START_SAVE_AREA + data + self.END_DIV + data = unquote_but_html(data, "utf-8") # HACK: nécessaire car minidom + # ne supporte pas les entities. utf-8 est codé en dur parce qu'on sait que + # TiddlyWiki enregistre dans ce codec. + dom = parseString(data) + for node in dom.documentElement.getElementsByTagName('div'): + self.add(Tiddler(element=node, parent=self)) + + return True + + def is_valid(self): + return self.valid + + def save(self, file=None, templatefile=None, set_modified=True, raise_exception=True): + if templatefile is None and self.file is None: + templatefile = path.join(path.split(__file__)[0], 'empty.html') + if templatefile is None: + if not hasattr(self, 'prefix') or not hasattr(self, 'suffix'): + raise IOError("Etat inconsistant: il faut le préfixe et le suffixe") + else: + self.__load_prefix_and_suffix(templatefile, raise_exception) + + if file is not None: self.__update_file(file) + tmppf = self.pf + '.tmp' + + try: + outf = open(tmppf, 'wb') + try: + outf.write(self.prefix) + for tiddler in self.tiddlers: + if tiddler.is_modified() and set_modified: + tiddler.set_modified() + outf.write(tiddler._formatElement().encode("utf-8")) + outf.write("\n") + outf.write(self.suffix) + finally: + outf.close() + os.rename(tmppf, self.pf) + except: + if raise_exception: raise + + def set_version(self, version): + self.version = version + def get_version(self): + return self.version + + def __len__(self): + return len(self.tiddlers) + def has_key(self, title): + return self.byname.has_key(title) + def __getitem__(self, indexOrTitle, default=_marker): + if isnum(indexOrTitle): + return self.tiddlers[indexOrTitle] + else: + if default is _marker: return self.byname[indexOrTitle] + else: return self.byname.get(indexOrTitle, default) + get = __getitem__ + def add(self, tiddler): + if not isinstance(tiddler, Tiddler): + raise ValueError("value doit être une instance de Tiddler") + title = tiddler.get_title() + if title in self.byname: + del self.tiddlers[self.byname[title]] + tiddler.parent = self + self.tiddlers.append(tiddler) + self.byname[title] = tiddler + def __delitem__(self, indexOrTitle): + if isnum(indexOrTitle): + tiddler = self.tiddlers[indexOrTitle] + index = indexOrTitle + else: + tiddler = self.byname[indexOrTitle] + index = self.tiddlers.index(tiddler) + del self.byname[tiddler.get_title()] + del self.tiddlers[index] + + def __repr__(self): + return repr(map(lambda t: t.get_title(), self.tiddlers)) + +################################################################################ + +class TwrcFile(ShConfigFile): + TWRC = "twrc" + + def __init__(self, file=None, raise_exception=True): + if file is None: + testdir = path.join(scriptdir, "test") + utoolsrcdir = path.join(path.expanduser("~"), ".utools") + if path.isdir(testdir): file = path.join(testdir, self.TWRC) + else: file = path.join(utoolsrcdir, self.TWRC) + raise_exception = False + ShConfigFile.__init__(self, file=file, raise_exception=raise_exception) + + DEFAULT_NAMES = "default_names" + MODIFIER = "modifier" + + def __p(self, p, refdir=None): + if refdir is not None: + if not path.isdir(refdir): refdir = path.split(refdir)[0] + p = path.normpath(path.expanduser(p)) + if refdir is not None: + p = path.join(refdir, p) + return p + + COMMA_PATTERN = re.compile(r'\s*,\s*') + def __vs(self, vs): + if isstr(vs): vs = self.COMMA_PATTERN.split(vs) + return tuple(vs) + def __csv(self, csv): + if isseq(csv): csv = ','.join(csv) + return ensure_unicode(csv) + + def get_default_names(self): + if self.has_key(self.DEFAULT_NAMES): return self.__vs(self[self.DEFAULT_NAMES]) + else: return TiddlyWiki.DEFAULT_NAMES + def set_default_names(self, default_names=None): + if default_names is None: + if self.has_key(self.DEFAULT_NAMES): del self[self.DEFAULT_NAMES] + else: + self[self.DEFAULT_NAMES] = self.__csv(default_names) + + def get_modifier(self): + if self.has_key(self.MODIFIER): return self[self.MODIFIER] + else: return os.environ.get('USER', 'TiddlyWiki.py') + def set_modifier(self, modifier=None): + if modifier is None: + if self.has_key(self.MODIFIER): del self[self.MODIFIER] + else: + self[self.MODIFIER] = modifier + +class TwCLI: + twrc = None + + def __newTiddlyWiki(self, file=None): + return TiddlyWiki(file, default_names=self.twrc.get_default_names(), raise_exception=True) + + twfile = None + def __twfile(self): + if self.twfile is None: + self.twfile = self.__newTiddlyWiki() + return self.twfile + + def __init__(self, twrc=None): + if not isinstance(twrc, TwrcFile): + twrc = TwrcFile(twrc) + self.twrc = twrc + + CONFOPT = 'c:f:' + CONFLOPT = ['config=', 'file='] + + def is_global_option(self, opt, value): + if opt in ('-c', '--config'): + self.twrc = TwrcFile(value) + elif opt in ('-f', '--file'): + self.twfile = self.__newTiddlyWiki(value) + else: + return False + return True + + def ADDTEXT(self, + title=None, text=None, + modifier=None, + set_tags=None, add_tags=None, remove_tags=None, + encoding=None, + argv=(), scriptname=None, + **kw): + u"""%(scriptname)s: Créer ou mettre à jour un tiddler + +USAGE + %(scriptname)s [options] title + +OPTIONS + -m text + Si le texte du tiddler est spécifié, on ne lance pas d'éditeur + -u modifier + Spécifier le nom de l'utilisateur qui fait la modification. Par défaut, + il s'agit de $USER + -t tag + tag: ajouter un tag, -tag: enlever un tag + -e encoding + Spécifier l'encoding du titre et du texte s'il sont spécifiés sur la + ligne de commande. Par défaut, on considère que les données sont + encodées en %(default_encoding)s""" + default_encoding = get_stdin_encoding() + opts, argv = getopt(argv, + self.CONFOPT + 'hm:u:t:e:', + self.CONFLOPT + ['help', 'text=', 'modifier=', 'tag=', 'encoding=']) + for opt, value in opts: + if self.is_global_option(opt, value): + pass + elif opt in ('-h', '--help'): + uprint(self.LIST.__doc__ % locals()) + sys.exit(0) + elif opt in ('-m', '--text'): + text = value + elif opt in ('-u', '--modifier'): + modifier = value + elif opt in ('-t', '--tag'): + if value.startswith('-'): + if remove_tags is None: remove_tags = [] + remove_tags.append(value[1:]) + else: + if value.startswith('+'): value = value[1:] + if add_tags is None: add_tags = [] + add_tags.append(value) + elif opt in ('-e', '--encoding'): + encoding = value + + if title is None: + if not argv: + raise ValueError("Il faut spécifier un titre pour le nouveau tiddler") + title = argv[0] + + edit = False + if text is None: + text = '' + edit = True + if modifier is None: + modifier = self.twrc.get_modifier() + if encoding is None: + encoding = default_encoding + title = ensure_unicode(title, encoding) + text = ensure_unicode(text, encoding) + + twfile = self.__twfile() + + new_tiddler = False + tiddler = twfile.get(title, None) + if tiddler is None: + new_tiddler = True + tiddler = Tiddler() + twfile.add(tiddler) + + tiddler.set_title(title) + tiddler.set_text(text) + tiddler.set_modifier(modifier) + if set_tags is not None: + tiddler.set_tags(set_tags) + if add_tags is not None: + for tag in add_tags: + tiddler.add_tag(tag) + if remove_tags is not None: + for tag in remove_tags: + tiddler.remove_tag(tag) + + if tiddler.is_modified(): + twfile.save() + if edit: + self.EDIT(tiddler=tiddler) + else: + if new_tiddler: enote(u"Ajout d'un nouveau tiddler '%s'" % title) + else: enote(u"Mise à jour du tiddler '%s'" % title) + + def ADDFILE(self, + file=None, title=None, + modifier=None, + set_tags=None, add_tags=None, remove_tags=None, + encoding=None, + argv=(), scriptname=None, + **kw): + u"""%(scriptname)s: Créer ou mettre à jour un tiddler à partir d'un fichier + +USAGE + %(scriptname)s [options] /path/to/file + +OPTIONS + -n title + Spécifier le titre du tiddler. Par défaut, il s'agit du nom du fichier + -u modifier + Spécifier le nom de l'utilisateur qui fait la modification. Par défaut, + il s'agit de $USER + -t tag + Ajouter un tag + Si le fichier a l'extension .js, on ajoute automatiquement le tag + systemConfig, sauf si un tag est spécifié + -e encoding + Spécifier l'encoding du fichier. Par défaut, on lit en %(default_encoding)s""" + default_encoding = get_editor_encoding() + opts, argv = getopt(argv, + self.CONFOPT + 'hn:u:t:e:', + self.CONFLOPT + ['help', 'title=', 'modifier=', 'tag=', 'encoding=']) + for opt, value in opts: + if self.is_global_option(opt, value): + pass + elif opt in ('-h', '--help'): + uprint(self.LIST.__doc__ % locals()) + sys.exit(0) + elif opt in ('-n', '--title'): + title = value + elif opt in ('-u', '--modifier'): + modifier = value + elif opt in ('-t', '--tag'): + if value.startswith('-'): + if remove_tags is None: remove_tags = [] + remove_tags.append(value[1:]) + else: + if value.startswith('+'): value = value[1:] + if add_tags is None: add_tags = [] + add_tags.append(value) + elif opt in ('-e', '--encoding'): + encoding = value + + if file is None: + if not argv: + raise ValueError("Il faut spécifier un fichier à importer") + file = argv[0] + if not path.exists(file): + raise IOError("Fichier inexistant: %s" % file) + + if title is None: + title = file + if modifier is None: + modifier = os.environ.get('USER', 'TiddlyWiki.py') + if path.splitext(file)[1] == '.js': + if set_tags is not None: + set_tags.append('systemConfig') + else: + if add_tags is None: add_tags = [] + add_tags.append('systemConfig') + if encoding is None: + encoding = defaut_encoding + + inf = open(file, 'rb') + try: + text = ensure_unicode(inf.read(), encoding) + finally: + inf.close() + + twfile = self.__twfile() + + new_tiddler = False + tiddler = twfile.get(title, None) + if tiddler is None: + new_tiddler = True + tiddler = Tiddler() + twfile.add(tiddler) + + tiddler.set_title(title) + tiddler.set_modifier(modifier) + if set_tags is not None: + tiddler.set_tags(set_tags) + if add_tags is not None: + for tag in add_tags: + tiddler.add_tag(tag) + if remove_tags is not None: + for tag in remove_tags: + tiddler.remove_tag(tag) + tiddler.set_text(text) + + if tiddler.is_modified(): + twfile.save() + if new_tiddler: enote(u"Ajout d'un nouveau tiddler '%s'" % title) + else: enote(u"Mise à jour du tiddler '%s'" % title) + + def REMOVE(self, + title=None, + argv=(), scriptname=None, + **kw): + u"""%(scriptname)s: Supprimer un tiddler + +USAGE + %(scriptname)s title""" + opts, argv = getopt(argv, + self.CONFOPT + 'hn:', + self.CONFLOPT + ['help', 'title=']) + for opt, value in opts: + if self.is_global_option(opt, value): + pass + elif opt in ('-h', '--help'): + uprint(self.LIST.__doc__ % locals()) + sys.exit(0) + elif opt in ('-n', '--title'): + title = value + + if title is None: + if not argv: + raise ValueError("Il faut spécifier le tiddler à supprimer") + title = argv[0] + + twfile = self.__twfile() + if twfile.has_key(title): + del twfile[title] + twfile.save() + enote(u"Suppression du tiddler '%s'" % title) + + def LIST(self, + showtext=False, + argv=(), scriptname=None, + **kw): + u"""%(scriptname)s: Lister les tiddlers + +USAGE + %(scriptname)s + +OPTIONS + -l Afficher aussi le contenu des tiddlers""" + opts, argv = getopt(argv, + self.CONFOPT + 'hl', + self.CONFLOPT + ['help', 'show-text']) + for opt, value in opts: + if self.is_global_option(opt, value): + pass + elif opt in ('-h', '--help'): + uprint(self.LIST.__doc__ % locals()) + sys.exit(0) + elif opt in ('-l', '--show-text'): + showtext = True + + twfile = self.__twfile() + if showtext: + for tiddler in twfile: + title = get_colored(u'>>> ' + tiddler.get_title(), 'b') + if tiddler.get_tags(): + title += " [%s]" % ', '.join(map(lambda t: get_colored(tiddler, 'y'), tiddler.get_tags())) + uprint(title) + + text = tiddler.get_text() + if text: uprint(text) + else: + for tiddler in twfile: + uprint(tiddler.get_title()) + + EDIT_TEMPLATE = u""" +EDIT: ---------------------------------------------------------------- +EDIT: Saisissez ou modifiez le titre et le corps du tiddler. +EDIT: +EDIT: - Les lignes commencant par 'EDIT:' seront supprimées automatiquement +EDIT: - La ligne tags: peut être modifiée si nécessaire. +EDIT: +EDIT: ----------------------------------------------------------------""" + + TAGS_PATTERN = re.compile(r'##\s*tags:\s*') + + def __nblines(self, s): + lines = s.split("\n") + nblines = len(lines) + if not lines[-1]: nblines -= 1 + return nblines + + def EDIT(self, + title=None, + tiddler=None, + argv=(), scriptname=None, + **kw): + u"""%(scriptname)s: Editer un tiddler + +USAGE + %(scriptname)s title""" + opts, argv = getopt(argv, + self.CONFOPT + 'h', + self.CONFLOPT + ['help']) + for opt, value in opts: + if self.is_global_option(opt, value): + pass + elif opt in ('-h', '--help'): + uprint(self.LIST.__doc__ % locals()) + sys.exit(0) + + twfile = self.__twfile() + if tiddler is None: + if title is None: + if not argv: + raise ValueError("Il faut spécifier le tiddler à éditer") + title = argv[0] + tiddler = twfile.get(title, None) + if tiddler is None: + raise ValueError("Tiddler non trouvé: %s" % title) + + template = u"" + template += u"## tags: %s\n" % twFormatTags(tiddler.get_tags()) + template += u"\n" + + title = tiddler.get_title() + template += u"%s\n" % title + setline = self.__nblines(template) + setcol = len(title) + + text = tiddler.get_text() + if text: template += u"\n%s\n" % text + + template += self.EDIT_TEMPLATE + lines = edit_template(template, 'EDIT:', setline, setcol).split('\n') + + new_tags = [] + parsing_tags = True + skip_empty = True + text = [] + for line in lines: + if skip_empty and not line: continue + if parsing_tags: + mot = self.TAGS_PATTERN.match(line) + if mot is not None: + new_tags.extend(twParseTags(line[mot.end():])) + continue + else: + parsing_tags = False + skip_empty = False + text.append(line) + text = "\n".join(text) + pos = text.find('\n\n') + if pos == -1: + title = text.replace('\n', ' ') + text = u'' + else: + title = text[:pos].replace('\n', ' ') + text = text[pos + 2:] + + tiddler.set_tags(new_tags) + tiddler.set_title(title) + tiddler.set_text(text) + + if tiddler.is_modified(): + twfile.save() + enote(u"Mise à jour du tiddler '%s'" % title) + +################################################################################ + +if __name__ == '__main__': + debug = False + action = None + argv = sys.argv[1:] + twCLI = TwCLI() + + # Essayer de determiner l'action avec le nom du script + if scriptname in ('twa', ): + action = 'addtext' + elif scriptname in ('twf', ): + action = 'addfile' + elif scriptname in ('twl', 'twll',): + if scriptname == 'twll': argv.insert(0, '-l') + action = 'list' + elif scriptname in ('twe', ): + action = 'edit' + + if action is None: + opts, argv = getopt(argv, + TwCLI.CONFOPT + 'hD', + TwCLI.CONFLOPT + ['help', 'debug']) + for opt, value in opts: + if opt in ('-h', '--help'): + uprint(__doc__ % dict(scriptname=scriptname)) + sys.exit(0) + elif twCLI.is_global_option(opt, value): + pass + elif opt in ('-D', '--debug'): + debug = True + + if not argv: + uprint(__doc__ % dict(scriptname=scriptname)) + sys.exit(0) + + action, argv = argv[0], argv[1:] + if action in ('addtext', 'add', 'a'): + action = 'addtext' + elif action in ('addfile', 'file', 'f'): + action = 'addfile' + elif action in ('remove', 'r'): + action = 'remove' + elif action in ('list', 'l', 'll'): + if action == 'll': argv.insert(0, '-l') + action = 'list' + elif action in ('edit', 'e'): + action = 'edit' + else: + eerror("Action inconnue: %s" % action) + sys.exit(1) + + if scriptname in ('TiddlyWiki.py', 'tw'): + # pour l'affichage de l'aide + scriptname = '%s %s' % (scriptname, action) + + try: + apply(getattr(twCLI, action.upper()), (), {'argv': argv, 'scriptname': scriptname}) + except Exception, e: + if debug: + eerror(e[0]) + import traceback + traceback.print_exc() + else: + die(e[0]) diff --git a/pyulib/migrate/wo.py b/pyulib/migrate/wo.py new file mode 100644 index 0000000..846fdfc --- /dev/null +++ b/pyulib/migrate/wo.py @@ -0,0 +1,366 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""Fonctions utilitaires pour gérer des projets WebObjects +""" + +try: True, False +except NameError: True, False = 1, 0 + +import os, sys, string, re, types +from os import path + +from base import basename, dirname, matches_name +from config import ShConfigFile, PListFile + +################################################## +# gestion des frameworks systèmes + +default_frameworks_for_woapp = ['JavaFoundation.framework', + 'JavaEOControl.framework', + 'JavaEOAccess.framework', + 'JavaWebObjects.framework', + 'JavaWOExtensions.framework', + 'JavaXML.framework', + 'JavaJDBCAdaptor.framework', + ] +default_frameworks_for_jcapp = ['JavaEOApplication.framework', + 'JavaEODistribution.framework', + 'JavaEOInterface.framework', + 'JavaEOInterfaceSwing.framework', + ] +system_frameworks = default_frameworks_for_woapp + default_frameworks_for_jcapp + \ + ['JavaDirectToWeb.framework', + 'JavaDTWGeneration.framework', + 'JavaEOGeneration.framework', + 'JavaEOProject.framework', + 'JavaEORuleSystem.framework', + 'JavaJNDIAdaptor.framework', + 'JavaOpenEJBActivation.framework', + 'JavaOpenEJB.framework', + 'JavaOpenORB.framework', + 'JavaOpenTM.framework', + 'JavaPlot.framework', + 'JavaWebServicesClient.framework', + 'JavaWebServicesGeneration.framework', + 'JavaWebServicesSupport.framework', + 'JavaWOJSPServlet.framework', + 'JavaWOSMIL.framework', + ] + +def is_system_framework(fp): + """retourner True si fp est un chemin vers un framework système + """ + return basename(fp) in system_frameworks + +def is_default_framework_for_woapp(fp): + return is_system_framework(fp) and basename(fp) in default_frameworks_for_woapp + +def is_default_framework_for_jcapp(fp): + return is_system_framework(fp) and basename(fp) in default_frameworks_for_jcapp + +################################################## +# gestion des exceptions + +class WOError(Exception): + """exception lancée si une erreur se produit dans l'une des fonctions de ce + module + """ + pass + +################################################## +# gestion des projets + +class WOProject: + """Classe de base des projets webobjects + + Cette classe est utilisée comme interface, pour indiquer les méthodes qui + existent dans les classes dérivées + """ + def projname(self): + """Retourner le nom du projet. + + Ce nom est utilisé comme nom de base pour le projet compilé, par exemple + projname.woa ou projname.framework + """ + raise NotImplementedError + + def projsubtype(self): + """Retourner le type de sous-projet: woapp, jcapp, framework + """ + raise NotImplementedError + + projtype_map = {'woapp': 'woapplication', + 'jcapp': 'woapplication', + 'wofrm': 'woframework', + } + def projtype(self): + """Retourner le type de projet: woapplication ou woframework + """ + return self.projtype_map[self.projsubtype()] + + def projdir(self): + """Retourner le répertoire absolu qui contient le projet + """ + raise NotImplementedError + + def projname_and_projtype(self): + """retourner le tuple (projname, projtype) + """ + return self.projname(), self.projtype() + +class WOJawotools(WOProject, ShConfigFile): + """Un projet jawotools + """ + def __init__(self, dir, raise_exception=True): + """Initialiser l'objet avec le répertoire dir + """ + ShConfigFile.__init__(self) + self.load(path.join(dir, 'build.properties'), raise_exception=raise_exception) + + def PROJECT_NAME(self): + try: return self['project_name'] + except KeyError: raise WOError, "Propriété project_name non définie dans le projet" + def PROJECT_TYPE(self): + try: return self['project_type'] + except KeyError: raise WOError, "Propriété project_type non définie dans le projet" + + def projname(self): return self.PROJECT_NAME() + def projsubtype(self): + type = string.lower(self.PROJECT_TYPE()) + if type.startswith("application"): + type = type.find("+javaclient") != -1 and "jcapp" or "woapp" + elif type.startswith("framework"): + type = "wofrm" + return type + + def projdir(self): return self.dirname + +class WOBuildConf(WOProject, ShConfigFile): + """Un projet wobuild.conf + """ + def __init__(self, dir, raise_exception=True): + """Initialiser l'objet avec le répertoire dir + """ + ShConfigFile.__init__(self) + self.load(path.join(dir, 'wobuild.conf'), raise_exception=raise_exception) + + def NAME(self): + try: return self['NAME'] + except KeyError: raise WOError, "Propriété NAME non définie dans le projet" + def TYPE(self): + try: return self['TYPE'] + except KeyError: raise WOError, "Propriété TYPE non définie dans le projet" + def TAG(self): return self.get('TAG', '') + + def projname(self): return self.NAME() + def projsubtype(self): + type = string.lower(self.TYPE()) + type = {'app': 'woapp', 'framework': 'wofrm'}.get(type, type) + return type + def projdir(self): return self.dirname + +class WOProjectMacOSX(WOProject, PListFile): + """Un projet de Project Builder sous Mac OS X + """ + def __init__(self, dir, raise_exception=True): + """Initialiser l'objet avec le répertoire dir + """ + PListFile.__init__(self) + + adir = path.abspath(dir) + bn = basename(adir) + if bn == "project.pbxproj" and matches_name("*.pbproj", basename(dirname(adir))): + # on a donné directement le fichier project.pbxproj + self.load(dir, raise_exception=raise_exception) + self._projdir = dirname(dirname(adir)) + elif matches_name("*.pbproj", bn): + # on a donné le nom du répertoire .pbproj + self.load(path.join(dir, 'project.pbxproj'), raise_exception=raise_exception) + self._projdir = dirname(adir) + else: + # on a donné le nom du répertoire du projet. il faut chercher le + # répertoire .pbproj + files = os.listdir(dir) + projects_filter = lambda file, bp=dir: matches_name('*.pbproj/', file, bp) + projects = filter(projects_filter, files) + if len(projects) != 1: + if raise_exception: + if len(projects): + raise WOError, "Il y a plusieurs répertoires .pbproj dans %s" % dir + else: + raise WOError, "Aucun répertoire .pbproj n'a été trouvé dans %s" % dir + return + projfile = path.join(dir, projects[0], 'project.pbxproj') + self.load(projfile, raise_exception=raise_exception) + self._projdir = adir + + self.group_for_oid = {} + + def is_oid(self, oid): + """déterminer si oid est un id d'objet + + n doit être de longeur 24 et n'être composé que de caractères hexadécimaux + """ + return re.match(r'[0-9a-fA-F]{24,24}$', oid) is not None + + def object(self, oid): + """retourner un objet si l'oid est valide, ou la valeur inchangée si ce + n'est pas un oid valide + """ + if self.is_oid(oid): + return self['objects'][oid] + else: + return oid + + def objects(self, oids): + """retourner une liste d'objet pour une liste d'oids + """ + return map(self.object, oids) + + def path(self, p='', o=None, convert_object=True): + """retourner un objet dont on donne le chemin + """ + if o is None: o = self.items + if type(o) is types.StringType: + o = self.object(o) + if p: + ps = string.split(p, '.') + count = len(ps) + for i in range(count): + p = ps[i] + last = i == count - 1 + + # obtenir la clé + if type(o) is types.DictType: + k = p + elif type(o) is types.ListType: + k = int(p) + else: + raise ValueError, "chemin invalide vers une valeur scalaire" + + # obtenir l'objet + o = o[k] + + # convertir éventuellement les références vers des objets + if convert_object or not last: + if type(o) is types.ListType: + o = self.objects(o) + elif type(o) is types.StringType: + o = self.object(o) + return o + + def __group_for(self, oid, start_at): + children = self.path('children', start_at, convert_object=False) + if oid in children: + return start_at + else: + for child in children: + group = self.object(child) + if group['isa'] in ('PBXGroup', 'PBXVariantGroup'): + found = self.__group_for(oid, child) + if found is not None: return found + return None + + def group_for(self, oid): + """retourner l'oid du groupe associé à l'objet d'id find_oid + """ + if not self.group_for_oid.has_key(oid): + self.group_for_oid[oid] = self.__group_for(oid, self.path('rootObject.mainGroup', convert_object=False)) + return self.group_for_oid[oid] + + def PRODUCT_NAME(self): + try: + if self.path('rootObject.targets.0.isa') in ('PBXApplicationTarget', 'PBXFrameworkTarget'): + return self.path('rootObject.targets.0.buildSettings.PRODUCT_NAME') + except KeyError: pass + raise WOError, "Impossible de déterminer le nom du produit" + + def TARGET0_ISA(self): + try: return self.path('rootObject.targets.0.isa') + except KeyError: pass + raise WOError, "Impossible de déterminer le type du target principal" + + def projname(self): return self.PRODUCT_NAME() + + projsubtype_map = {'PBXApplicationTarget': 'woapp', + 'PBXFrameworkTarget': 'wofrm', + } + def projsubtype(self): + subtype = self.projsubtype_map[self.TARGET0_ISA()] + if subtype == 'woapp': + # tester si les frameworks pour jcapp sont présents + pass + return subtype + + def projdir(self): return self._projdir + +class WOProjectWindows(WOProject, PListFile): + """Un projet de Project Builder sous Windows + """ + def __init__(self, dir, raise_exception=True): + """Initialiser l'objet avec le répertoire dir + """ + PListFile.__init__(self) + + adir = path.abspath(dir) + if basename(adir) == "PB.project": + # on a donné le nom du fichier PB.project + self.load(dir, raise_exception=raise_exception) + else: + # on a donné le nom du répertoire du projet. il faut chercher le + # fichier PB.project + self.load(path.join(dir, 'PB.project'), raise_exception=raise_exception) + + def PROJECTNAME(self): return self['PROJECTNAME'] + def PROJECTTYPE(self): return self['PROJECTTYPE'] + + def projname(self): return self.PROJECTNAME() + + projsubtype_map = {'Application': 'woapp', # apparemment, client lourd java / wo4.5 + 'EOApplication': 'woapp', # client lourd objective c / wo4.5 + 'JavaWebObjectsApplication': 'woapp', + 'JavaWebObjectsFramework': 'wofrm', + } + def projsubtype(self): + subtype = self.projsubtype_map[self.PROJECTTYPE()] + if subtype == 'woapp': + # tester si les frameworks pour jcapp sont présents + pass + return subtype + + def projdir(self): return self.dirname + +def wo_projname_and_projtype(ap): + """Obtenir le nom du projet WebObjects pour le répertoire absolu ap et le + type de projet: app ou framework + + Si ce répertoire ne contient pas de projet WebObjects ou en contient + plusieurs, retourner None. + """ + # tester si le projet est un projet jawotools + proj = WOJawotools(ap, raise_exception=False) + if proj.valid: + try: return proj.projname_and_projtype() + except WOError: pass + + # tester si le projet est un projet wobuild + proj = WOBuildConf(ap, raise_exception=False) + if proj.valid: + try: return proj.projname_and_projtype() + except WOError: pass + + # tester s'il s'agit d'un projet ProjectBuilder/MacOSX + # ce test ne fonctione que s'il n'y a qu'un seul projet dans le répertoire + proj = WOProjectMacOSX(ap, raise_exception=False) + if proj.valid: + try: return proj.projname_and_projtype() + except WOError: pass + + # tester s'il s'agit d'un projet ProjectBuilder/Windows + proj = WOProjectWindows(ap, raise_exception=False) + if proj.valid: + try: return proj.projname_and_projtype() + except WOError: pass + + # sinon, on déclare forfait + return None, None diff --git a/pyulib/setup.py b/pyulib/setup.py new file mode 100755 index 0000000..0302980 --- /dev/null +++ b/pyulib/setup.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +NAME = 'pyulib' +VERSION = None +DESCRIPTION = 'Librairies python de utools' +AUTHOR = 'Jephte CLAIN' +EMAIL = 'Jephte.Clain@univ-reunion.fr' +MODULES = ('i_need_py23', 'i_need_py24', 'i_need_py25', 'i_need_py26', + 'ULIB_CONFIG', + ) +SRCDIR = 'src' +PACKAGE_DIR = {'': SRCDIR} +PACKAGE_DATA = {} +PACKAGES = [] +SCRIPTS = [] + +import os, sys, re, fnmatch +from os import path + +RE_VERSION = re.compile(r'(\d+(?:\.\d+)*)(?:-r(\d+/\d+/\d+))?') +def get_version(basedir=None): + if basedir is None: + basedir = path.split(path.abspath(sys.argv[0]))[0] + version_txt = path.join(basedir, 'VERSION.txt') + if not path.isfile(version_txt): return '' + try: + inf = open(version_txt, 'rb') + try: line = inf.readline() + finally: inf.close() + except: + return '' + mo = RE_VERSION.match(line) + if not mo: return '' + return mo.group(1) + +def findf(spec, bp): + """Transformer le package bp en chemin, puis chercher récursivement les + fichiers correspondant à la spécification spec à partir de SRCDIR/bp + """ + files = [] + bp = bp.replace(".", "/") + bpdir = path.join(SRCDIR, bp) + bpnames = os.listdir(bpdir) + for specname in fnmatch.filter(bpnames, spec): + specfile = path.join(bpdir, specname) + if path.isfile(specfile): + files.append(specname) + else: + for dirpath, dirnames, filenames in os.walk(specfile): + dirnames.remove('.svn') + dirpath = dirpath[len(bpdir)+1:] + files.extend([path.join(dirpath, filename) for filename in filenames]) + return files + +def fixp(p, bp): + """Transformer le package bp en chemin, puis exprimer le chemin relatif p + par rapport au chemin du package, puis ajouter SRCDIR/ devant le chemin + """ + bp = bp.replace(".", "/") + return path.join(SRCDIR, bp, p) +def addp(name, data=()): + """Ajouter un package, avec ses fichiers de données + """ + global PACKAGES, PACKAGE_DATA + PACKAGES.append(name) + if data: + files = [] + for spec in data: + files.extend(findf(spec, name)) + PACKAGE_DATA[name] = files +def adds(name, scripts=()): + """Ajouter des scripts contenus dans un package + """ + global SCRIPTS + if scripts: + SCRIPTS.extend(map(lambda s: fixp(s, name), scripts)) + +if VERSION is None: VERSION = get_version() +addp('ulib') +addp('ulib.all') +addp('ulib.base') +addp('ulib.ext') +addp('ulib.ext.optik141', ['README.txt']) +addp('ulib.ext.simplejson', ['_speedups.c']) +addp('ulib.ext.tarfile', ['README.txt']) +addp('ulib.ext.web') +addp('ulib.ext.web.wsgiserver', ['LICENSE.txt']) +addp('ulib.ext.web.contrib') +addp('ulib.formats') +addp('ulib.gae') +addp('ulib.json') +addp('ulib.optparse') +addp('ulib.sa') +addp('ulib.tasks') +addp('ulib.templ') +addp('ulib.web') +addp('ulib.p') +addp('ulib.p.templ') +addp('ulib.p.uinc') +addp('ulib.p.vcs') +addp('ulib.p.wop') + +if __name__ == '__main__': + from distutils.core import setup + setup(name=NAME, version=VERSION, + description=DESCRIPTION, author=AUTHOR, author_email=EMAIL, + py_modules=MODULES, + package_dir=PACKAGE_DIR, package_data=PACKAGE_DATA, packages=PACKAGES, + scripts=SCRIPTS, + ) diff --git a/pyulib/src/ULIB_CONFIG.py b/pyulib/src/ULIB_CONFIG.py new file mode 100644 index 0000000..f860e0c --- /dev/null +++ b/pyulib/src/ULIB_CONFIG.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""Configuration de pyulib. + +Ce module contient des variables qui servent à configurer le comportement de +pyulib. Un module du même nom situé avant sys.path peut contenir les mêmes +valeurs. Ceci permet de configurer la librairie sans changer le contenu de ce +fichier, qui sert juste à documenter ce qui peut être configuré. +""" + +# Liste des modules de base qui sont importés automatiquement avec +# from ulib.all import * +# Seuls sont valides les modules de ulib.base. Il faut les mentionner de façon +# relative, e.g. 'uio' au lieu de 'ulib.base.uio' +# Par défaut, le contenu de tous les modules est importé. +#MODULES = () + +# L'importation de ulib.base.encoding provoque-t-il la configuration du locale courant? +#SET_LOCALE = True + +# Encoding par défaut, s'il est impossible de le détecter autrement. +#DEFAULT_INPUT_ENCODING = 'utf-8' +#DEFAULT_OUTPUT_ENCODING = 'utf-8' + +# Faut-il supprimer le répertoire courant de sys.path? +#CLEAN_SYSPATH = True diff --git a/pyulib/src/ULIB_CONFIG.pyc b/pyulib/src/ULIB_CONFIG.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02b9ca3566fc4f0041f08394ae73396522c7d065 GIT binary patch literal 496 zcmY*V%}N6?5Z-!G7kq~~_8_}=5fK;Ef>c53!ON0nr`?oIvSyM>-^OeE6v94%ui_Ip z*=;EXW-`e)-#0%$zZdBne80$Ke@1-YvQf&eN|JZ>qvUmb68|0}hgW7SGfB U$wSs|{29})hxD6sj)^V#1HR&*%K!iX literal 0 HcmV?d00001 diff --git a/pyulib/src/i_need_py23.py b/pyulib/src/i_need_py23.py new file mode 100644 index 0000000..5a2cebd --- /dev/null +++ b/pyulib/src/i_need_py23.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""L'importation de ce module permet de s'assurer que la version de python est +2.3 ou supérieure (mais pas 3.x) +""" + +from ulib.base.pversion import ensure_version +from ulib.base.control import die +try: ensure_version("2.3") +except: die() diff --git a/pyulib/src/i_need_py23.pyc b/pyulib/src/i_need_py23.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92499ed17ced8d5b7ed98bbc5a48f8b0f5ec37a7 GIT binary patch literal 458 zcmY*V%}N6?5T5L|Rf;avC&;bqWdrR^M7#d*rcEbup9vE zGr$Do0Nxm|dy@fU3@HO(hQWgz!fif^l7`@Fsv2k8Pz7ZTm&j}6jg_57?yzkz#G0R0 z-ghmw{Ha5(EB=hF-_5%|tci&}jAzB1+m8FreZIE}iNN!wQl7iY^Lg=lGFCriIitNH zBR~^X%bnV`o>E;Z^f8j)bv}&K@f*rg+PW;mF`YZDR>i9FShyW~K~)Gx|9Z7Gp|yGu z7v{152mX{&v9=A0$68m)h!^X`_T^rfE?BF*aD7}Pd|L}u8bn#Tel{=Y&HF?@+m4^d Q8~Ou+Q8Hl@mO@7U3zz|OG5`Po literal 0 HcmV?d00001 diff --git a/pyulib/src/i_need_py24.py b/pyulib/src/i_need_py24.py new file mode 100644 index 0000000..f31b7d2 --- /dev/null +++ b/pyulib/src/i_need_py24.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""L'importation de ce module permet de s'assurer que la version de python est +2.4 ou supérieure (mais pas 3.x) +""" + +from ulib.base.pversion import ensure_version +from ulib.base.control import die +try: ensure_version("2.4") +except: die() diff --git a/pyulib/src/i_need_py25.py b/pyulib/src/i_need_py25.py new file mode 100644 index 0000000..0221de8 --- /dev/null +++ b/pyulib/src/i_need_py25.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""L'importation de ce module permet de s'assurer que la version de python est +2.5 ou supérieure (mais pas 3.x) +""" + +from ulib.base.pversion import ensure_version +from ulib.base.control import die +try: ensure_version("2.5") +except: die() diff --git a/pyulib/src/i_need_py26.py b/pyulib/src/i_need_py26.py new file mode 100644 index 0000000..1185b2f --- /dev/null +++ b/pyulib/src/i_need_py26.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""L'importation de ce module permet de s'assurer que la version de python est +2.6 ou supérieure (mais pas 3.x) +""" + +from ulib.base.pversion import ensure_version +from ulib.base.control import die +try: ensure_version("2.6") +except: die() diff --git a/pyulib/src/uapps/__init__.py b/pyulib/src/uapps/__init__.py new file mode 100644 index 0000000..5a00fb6 --- /dev/null +++ b/pyulib/src/uapps/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 diff --git a/pyulib/src/uapps/jclain_license.txt b/pyulib/src/uapps/jclain_license.txt new file mode 100644 index 0000000..4a6991a --- /dev/null +++ b/pyulib/src/uapps/jclain_license.txt @@ -0,0 +1,104 @@ +LICENCE d'utilisation de @@projname@@ + + Copyright (c) @@year@@, Jephte CLAIN + All rights reserved + + Contact: Jephte.Clain@univ-reunion.fr + + Preamble + + The intent of this document is to state the conditions under which a Package + may be copied, such that the Copyright Holder maintains some semblance of + artistic control over the development of the package, while giving the users + of the package the right to use and distribute the Package in a more-or-less + customary fashion, plus the right to make reasonable modifications. + + Definitions: + + "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through textual + modification. + + "Standard Version" refers to such a Package if it has not been modified, or + has been modified in accordance with the wishes of the Copyright Holder. + + "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. + + "You" is you, if you're thinking about copying or distributing this Package. + + "Reasonable copying fee" is whatever you can justify on the basis of media + cost, duplication charges, time of people involved, and so on. (You will not + be required to justify it to the Copyright Holder, but only to the computing + community at large as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + + 1. You may make and give away verbatim copies of the source form of the + Standard Version of this Package without restriction, provided that you + duplicate all of the original copyright notices and associated disclaimers. + + 2. You may apply bug fixes, portability fixes and other modifications derived + from the Public Domain or from the Copyright Holder. A Package modified in + such a way shall still be considered the Standard Version. + + 3. You may otherwise modify your copy of this Package in any way, provided + that you insert a prominent notice in each changed file stating how and when + you changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or an + equivalent medium, or placing the modifications on a major archive site such + as ftp.uu.net, or by allowing the Copyright Holder to include your + modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict with + standard executables, which must also be provided, and provide a separate + manual page for each non-standard executable that clearly documents how it + differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + + 4. You may distribute the programs of this Package in object code or + executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where to + get the Standard Version. + + b) accompany the distribution with the machine-readable source of the + Package with your modifications. + + c) accompany any non-standard executables with their corresponding Standard + Version executables, giving the non-standard executables non-standard names, + and clearly documenting the differences in manual pages (or equivalent), + together with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + + 5. You may charge a reasonable copying fee for any distribution of this + Package. You may charge any fee you choose for support of this Package. You + may not charge a fee for this Package itself. However, you may distribute this + Package in aggregate with other (possibly commercial) programs as part of a + larger (possibly commercial) software distribution provided that you do not + advertise this Package as a product of your own. + + 6. The scripts and library files supplied as input to or produced as output + from the programs of this Package do not automatically fall under the + copyright of this Package, but belong to whomever generated them, and may be + sold commercially, and may be aggregated with this Package. + + 7. C or perl subroutines supplied by you and linked into this Package shall + not be considered part of this Package. + + 8. The name of the Copyright Holder may not be used to endorse or promote + products derived from this software without specific prior written permission. + + 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. diff --git a/pyulib/src/uapps/plbck.py b/pyulib/src/uapps/plbck.py new file mode 100755 index 0000000..e6c4069 --- /dev/null +++ b/pyulib/src/uapps/plbck.py @@ -0,0 +1,449 @@ +#!/usr/bin/env python +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +u"""%(scriptname)s: Gérer des fichiers de sauvegarde + +USAGE + %(scriptname)s [options] bckdir + +Les fichiers de sauvegarde sont de la forme [PREFIX]yyyymmdd.n[SUFFIX] +En fonction des fichiers présents dans bckdir, ce script peut afficher: +- le nom du fichier pour la prochaine sauvegarde +- le nom du fichier de la dernière sauvegarde +- le nom des fichiers de sauvegarde à supprimer selon la politque actuelle + +Si dans les fichiers du répertoire, il y a plusieurs couples (PREFIX, SUFFIX), +alors les traitements sont répétés pour chacun des couples existant, qui forment +chacun un groupe. Si un préfixe et un suffixe sont spécifiés avec les options -P +et -S, alors seuls les fichiers correspondant à ce groupe sont traités. + +OPTIONS + -c Afficher le nom à utiliser pour la prochaine sauvegarde (CURRENT) + -p Afficher le nom de la dernière sauvegarde effectuée (PREVIOUS), ou une + ligne vide s'il n'y a pas encore de sauvegardes. + -D Afficher les noms des sauvegardes à supprimer (DELETES). L'affichage se + fait un fichier par ligne, et se termine par une ligne vide. + Par défaut, afficher -c -p -D + -e Afficher les valeurs CURRENT, PREVIOUS et DELETES comme des variables + shell au lieu de lignes. DELETES est affiché sous forme de tableau. Par + exemple: + index=0 + CURRENT='backup-20120507.0.tar.gz' + PREVIOUS='backup-20120506.0.tar.gz' + DELETES=('backup-20120505.0.tar.gz' 'backup-20120504.0.tar.gz' 'backup-20120503.0.tar.gz') + Chaque groupe se voit attribuer un index commençant à 0. + --bcmd + Dans le mode -e, spécifier une commande à insérer avant le premier + groupe. Quand cette commande est lancée, index==-1 + --cmd + Dans le mode -e, spécifier une commande à insérer après chaque groupe. + --ecmd + Dans le mode -e, spécifier une commande à insérer après le dernier + groupe. + -P PREFIX + -S SUFFIX + Si aucun fichier n'est trouvé, valeur par défaut du préfixe et du + suffixe des fichiers de sauvegarde. + --prefix PREFIX + --suffix SUFFIX + Spécifier le préfixe et le suffixe des fichiers de sauvegarde sous forme + d'expressions régulières. Seuls les fichiers correspondant à ces valeurs + sont considérés. + Ces valeurs ne permettent pas de déterminer la valeur du préfixe et du + suffixe à utiliser si aucun fichier n'existe. Il faut utiliser les + options -P et -S pour cela. + Ces options n'ont pas de valeur par défaut, sauf si les options -P et -S + sont spécifiées. + -N max_days[=%(DEFAULT_MAX_DAYS)i] + Nombre maximum de *jours* de sauvegarde gardés, étant entendu que l'on + peut faire plusieurs sauvegardes par jour. C'est en fonction de cette + valeur que le calcul des fichiers à supprimer est effectué. + Une valeur de 0 signifie que l'on ne veut garder aucune sauvegarde. + -M max_backups[=%(DEFAULT_MAX_BACKUPS)i] + Nombre total maximum de fichiers de sauvegarde gardés, sans tenir compte + du jour auquel la sauvegarde a été effectuée. C'est en fonction de cette + valeur que le calcul des fichiers à supprimer est effectué. + Cette valeur ne doit pas être inférieure à max_days.""" + +DEFAULT_MAX_DAYS=15 +DEFAULT_MAX_BACKUPS=30 + +import os, sys, re +from os import path + +from ulib.all import * + +class Backup(object): + """Ensemble de fichier de sauvegardes qui partagent un même préfixe et un même suffixe + """ + bm = None + bckdir = None + prefix = None + suffix = None + + bydates = None + valid = None + current = None + previous = None + deletes = None + + def __init__(self, bm, bckdir, prefix=None, suffix=None): + self.bm = bm + self.bckdir = bckdir + if prefix is None: prefix = '' + if suffix is None: suffix = '' + self.prefix = prefix + self.suffix = suffix + self.bydates = {} + self.valid = False + + RE_BCKFILE = re.compile(r'(.*)(\d{4})(\d{2})(\d{2})\.(\d+)(.*)') + def add(self, bckname): + """Ajouter un fichier de sauvegarde à la liste. + + bckname est le nom du fichier situé dans self.bckdir + """ + mo = self.RE_BCKFILE.match(bckname) + if mo is None: raise ValueError("%s: n'est pas un fichier de sauvegarde valide" % bckname) + + prefix, year, month, day, num, suffix = mo.groups() + if prefix != self.prefix or suffix != self.suffix: + raise ValueError("(prefix, suffix) ne sont pas cohérents: %r != %r", + (prefix, suffix), + (self.prefix, self.suffix), + ) + + year, month, day, num = map(int, (year, month, day, num)) + date = Date(day, month, year) + + bckinfo = {'file': path.join(self.bckdir, bckname), 'date': date, 'num': num} + bynums = self.bydates.get(date, None) + if bynums is None: + bynums = {} + self.bydates[date] = bynums + bynums[num] = bckinfo + self.valid = False + + def __build_bckinfo(self, date=None, num=0): + """Contruire un dictionnaire bckinfo à partir de self.prefix, + self.suffix et la date et le numéro spécifiés. + """ + if date is None: date = Date() + bckname = "%s%04i%02i%02i.%i%s" % ( + self.prefix, + date.year, date.month, date.day, num, + self.suffix, + ) + bckinfo = {'file': path.join(self.bckdir, bckname), 'date': date, 'num': num} + return bckinfo + + def __sortfunc(self, bckinfo1, bckinfo2): + """Comparer deux dictionnaires bckinfo + """ + c = cmp(bckinfo1['date'], bckinfo2['date']) + if c != 0: return c + c = cmp(bckinfo1['num'], bckinfo2['num']) + return c + + def __sorted(self, bydates): + """Créer une liste inversement triée de dictionnaires bckinfo à partir + de bydates. + """ + bckinfos = [] + dates = bydates.keys() + dates.sort() + for date in dates: + nums = bydates[date].keys() + nums.sort() + for num in nums: + bckinfos.append(bydates[date][num]) + bckinfos.reverse() + return bckinfos + + def __compute_values(self): + """Calculer current, previous et deletes en fonction de la valeur + actuelle de self.bydates + """ + bydates = self.bydates.copy() + bckinfos = self.__sorted(bydates) + now = Date() + + # calculer previnfo et curinfo + if bckinfos: + previnfo = bckinfos[0] + if previnfo['date'] == now: + curinfo = self.__build_bckinfo(now, previnfo['num'] + 1) + else: + curinfo = self.__build_bckinfo(now, 0) + else: + previnfo = None + curinfo = self.__build_bckinfo(now, 0) + + ## calculer delinfos + max_days, max_backups = self.bm.max_days, self.bm.max_backups + delinfos = [] + dates = bydates.keys() + dates.sort() + # d'abord supprimer tous les fichiers correspondant aux dates périmées + if len(dates) > max_days: + if max_days > 0: dates2delete = dates[:-max_days] + else: dates2delete = dates + for date in dates2delete: + for num in bydates[date].keys(): + delinfos.append(bydates[date][num]) + del bydates[date][num] + # ensuite, supprimer les sauvegardes journalières supplémentaires + # jusqu'à ce qu'on ne dépasse plus le nombre de fichiers de sauvegarde + # maximum. + bckinfos = self.__sorted(bydates) + while len(bckinfos) > max_backups: + dates = bydates.keys() + dates.sort() + for date in dates: + nums = bydates[date].keys() + nums.sort() + if len(nums) <= 1: continue + num = nums[0] + delinfos.append(bydates[date][num]) + del bydates[date][num] + break + bckinfos = self.__sorted(bydates) + delinfos.sort(self.__sortfunc) + + # fin du traitement + self.current = curinfo['file'] + if previnfo is None: self.previous = None + else: self.previous = previnfo['file'] + self.deletes = [delinfo['file'] for delinfo in delinfos] + self.valid = True + + def get_current(self): + if not self.valid: self.__compute_values() + return self.current + + def get_previous(self): + if not self.valid: self.__compute_values() + return self.previous + + def get_deletes(self): + if not self.valid: self.__compute_values() + return self.deletes + +class BackupManager(object): + """Objet permettant de classer un ensemble de fichiers de sauvegarde qui ont + le même préfixe et le même suffixe. + """ + bckdir = None + re_prefix = None + re_suffix = None + def_prefix = None + def_suffix = None + max_days = DEFAULT_MAX_DAYS + max_backups = DEFAULT_MAX_BACKUPS + backups = None + + def __init__(self, bckdir, re_prefix=None, re_suffix=None, def_prefix=None, def_suffix=None): + self.bckdir = bckdir + if re_prefix is not None: self.re_prefix = re_prefix + if re_suffix is not None: self.re_suffix = re_suffix + if def_prefix is not None: self.def_prefix = def_prefix + if def_suffix is not None: self.def_suffix = def_suffix + + def __fix_max_vars(self): + max_days, max_backups = self.max_days, self.max_backups + if max_days < 0: max_days = 0 + if max_backups < max_days: max_backups = max_days + self.max_days, self.max_backups = max_days, max_backups + + RE_BCKFILE = re.compile(r'(.*)(\d{8}\.\d+)(.*)') + def __build_backups(self): + backups = {} + for bckname in os.listdir(self.bckdir): + # vérifier si le fichier a la forme d'un fichier de sauvegarde + mo = self.RE_BCKFILE.match(bckname) + if mo is None: continue + + # si re_prefix et re_suffix sont spécifiés, il doivent correspondre + prefix, tag, suffix = mo.groups() + if self.re_prefix is not None: + if self.re_prefix.match(prefix) is None: continue + if self.re_suffix is not None: + if self.re_suffix.match(suffix) is None: continue + + # enregistrer le fichier trouvé + backup = backups.get((prefix, suffix), None) + if backup is None: + backup = Backup(self, self.bckdir, prefix, suffix) + backups[(prefix, suffix)] = backup + backup.add(bckname) + + # si aucun fichier n'a été trouvé, + if not backups: + prefix = self.def_prefix + suffix = self.def_suffix + backups[(prefix, suffix)] = Backup(self, self.bckdir, prefix, suffix) + return backups + + def get_backups(self): + if self.backups is None: + self.__fix_max_vars() + self.backups = self.__build_backups() + return self.backups.values() + +class OutputManager(object): + """Affichage de current, previous, deletes + """ + shell = None + bcmd = None + cmd = None + ecmd = None + + def __init__(self, shell=False, bcmd=None, cmd=None, ecmd=None): + self.shell = shell + if bcmd is not None: self.bcmd = bcmd + if cmd is not None: self.cmd = cmd + if ecmd is not None: self.ecmd = ecmd + + def header(self, index, first=False): + if self.shell: + print "index=%i" % index + if first and self.bcmd is not None: + print self.bcmd + + def quote_shell(self, s): + return "'%s'" % s.replace("'", "'\\''") + + def print_current(self, current): + if self.shell: + print "CURRENT=%s" % (self.quote_shell(current)) + else: + print current + + def print_previous(self, previous): + if previous is None: previous = "" + if self.shell: + print "PREVIOUS=%s" % (self.quote_shell(previous)) + else: + print previous + + def print_deletes(self, deletes): + deletes = seqof(deletes) + if self.shell: + deletes = [self.quote_shell(s) for s in deletes] + print "DELETES=(%s)" % " ".join(deletes) + else: + for delete in deletes: + print delete + print + + def footer(self, index, last=False): + if self.shell: + if not last and self.cmd is not None: + print self.cmd + elif last and self.ecmd is not None: + print self.ecmd + +def display_help(): + uprint(__doc__ % globals()) + +def run_plbck(): + options, longoptions = build_options([ + (None, 'help', "Afficher l'aide"), + ('c', 'current'), + ('p', 'previous'), + ('D', 'deletes'), + ('e', 'shell'), + (None, 'bcmd='), + (None, 'cmd='), + (None, 'ecmd='), + ('P:', 'def-prefix='), + ('S:', 'def-suffix='), + (None, ('prefix=', 're-prefix=')), + (None, ('suffix=', 're-suffix=')), + ('N:', ('days=', 'max-days=')), + ('M:', ('count=', 'max-backups=')), + ]) + options, args = get_args(None, options, longoptions) + + show_current = False + show_previous = False + show_deletes = False + show_auto = True + shell_vars = False + bcmd = None + cmd = None + ecmd = None + def_prefix = None + def_suffix = None + re_suffix = None + re_prefix = None + max_days = None + max_backups = None + for option, value in options: + if option in ('--help', ): + display_help() + sys.exit(0) + elif option in ('-c', '--current'): + show_current = True + show_auto = False + elif option in ('-p', '--previous'): + show_previous = True + show_auto = False + elif option in ('-D', '--deletes'): + show_deletes = True + show_auto = False + elif option in ('-e', '--shell'): + shell_vars = True + elif option in ('--bcmd', ): + bcmd = value + elif option in ('--cmd', ): + cmd = value + elif option in ('--ecmd', ): + ecmd = value + elif option in ('-P', '--def-prefix'): + def_prefix = value + if re_prefix is None: + re_prefix = re.compile('%s$' % re.escape(def_prefix)) + elif option in ('-S', '--def-suffix'): + def_suffix = value + if re_suffix is None: + re_suffix = re.compile('%s$' % re.escape(def_suffix)) + elif option in ('--prefix', '--re-prefix'): + # la correspondance avec re.match() et doit toujours être complète + if value[-1:] != '$': value += '$' + re_prefix = re.compile(value) + elif option in ('--suffix', '--re-suffix'): + # la correspondance avec re.match() et doit toujours être complète + if value[-1:] != '$': value += '$' + re_suffix = re.compile(value) + elif option in ('-N', '--days', '--max-days'): + max_days = int(value) + elif option in ('-M', '--count', '--max-backups'): + max_backups = int(value) + + if show_auto: + show_current = True + show_previous = True + show_deletes = True + + if args: bckdir = args[0] + else: bckdir = '.' + + bm = BackupManager(bckdir, re_prefix, re_suffix, def_prefix, def_suffix) + if max_days is not None: bm.max_days = max_days + if max_backups is not None: bm.max_backups = max_backups + om = OutputManager(shell_vars, bcmd, cmd, ecmd) + + index = -1 + om.header(index, True) + for backup in bm.get_backups(): + index += 1 + om.header(index) + if show_current: om.print_current(backup.get_current()) + if show_previous: om.print_previous(backup.get_previous()) + if show_deletes: om.print_deletes(backup.get_deletes()) + om.footer(index) + om.footer(index, True) + +if __name__ == '__main__': + run_plbck() diff --git a/pyulib/src/uapps/plver.py b/pyulib/src/uapps/plver.py new file mode 100755 index 0000000..7c8824d --- /dev/null +++ b/pyulib/src/uapps/plver.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +u"""%(scriptname)s: Afficher la dernière version d'un ensemble de fichiers +USAGE + %(scriptname)s [/path/to/]basename +OPTIONS + -r Inverser le rôle de cet utilitaire: afficher toutes les versions des + fichiers plutôt que la dernière + -s suffix + Ajouter suffix à la liste des suffixes qui doivent être enlevés au nom + de fichier avant de trouver la version. + -n Ne pas utiliser une liste de suffixes par défaut qui convient pour les + fichiers d'archives et d'export: + (war, jar, ear, zip, tar[.gz|.bz2], t[gz|bz2], sql, ldif) + -f INPUT-FILE + Au lieu de lister les fichiers, prendre la liste dans INPUT-FILE. -f - + signifie lire sur stdin.""" + +import os, sys, re +from os import path +from glob import glob + +from ulib.all import * + +SUFFIXES = (".war", ".jar", ".ear", ".zip", ".gz", ".bz2", ".tar", ".tgz", ".tbz2", + ".sql", ".ldif", + ) +def strip_suffixes(p, suffixes=SUFFIXES): + suffix_striped = True + while suffix_striped: + suffix_striped = False + for suffix in suffixes: + if p.endswith(suffix): + p = p[:len(p) - len(suffix)] + suffix_striped = True + return p + +RE_ARCHIVE = re.compile(r'(?i)\.(war|jar|ear|zip|tar|tar\.gz|tgz|tar\.bz2|tbz2)$') +def is_archive(p): + return RE_ARCHIVE.search(p) is not None +def strip_archive_ext(p): + mo = RE_ARCHIVE.search(p) + if mo is not None: + p = p[:mo.start(0)] + return p + +RE_PATH_VERSION = re.compile(r'(?i)(?:-|_|\.)' # préfixe: - ou . + r'(\d+(?:(-|_|\.)\d+(?:\2\d+)*)?)' # version + r'(?:(a|alpha|b|beta|rc)(\d*))?' # type + r'$' # + ) +RE_NUMBERS = re.compile(r'\d+') +TYPE_MAPPINGS = {'a': 'alpha', 'b': 'beta'} +TYPES = ['alpha', 'beta', None, 'rc'] +def has_version(p): + return RE_PATH_VERSION.search(p) is not None +def get_version(p): + mo = RE_PATH_VERSION.search(p) + if mo is None: return None, None, None + else: + vns = map(int, RE_NUMBERS.findall(mo.group(1))) + type = mo.group(3) + if type is not None: type = type.lower() + type = TYPE_MAPPINGS.get(type, type) + n = mo.group(4) + if n is not None: n = int(n) + return vns, type, n +def strip_version(p): + mo = RE_PATH_VERSION.search(p) + if mo is not None: + p = p[:mo.start(0)] + return p + +class PathVersion: + def __init__(self, suffixes=SUFFIXES): + self.suffixes = suffixes + + def cmp(self, p0, p1): + p0 = strip_suffixes(p0, self.suffixes) + p1 = strip_suffixes(p1, self.suffixes) + + vns0, type0, n0 = get_version(p0) + vns1, type1, n1 = get_version(p1) + + c = cmp(vns0, vns1) + if c == 0: c = cmp(TYPES.index(type0), TYPES.index(type1)) + if c == 0: c = cmp(n0, n1) + return c + +def display_help(): + uprint(__doc__ % globals()) + +def run_plver(): + all = False + reverse = False + suffixes = list(SUFFIXES) + inf = None + + opts, argv = get_args(None, 'arns:f:', + ['help', 'all', 'reverse', 'no-suffixes', 'suffix=', 'file=']) + for opt, value in opts: + if opt in ('--help', ): + display_help() + sys.exit(0) + elif opt in ('-a', '--all'): + all = True + elif opt in ('-r', '--reverse'): + reverse = True + elif opt in ('-n', '--no-suffixes'): + suffixes = [] + elif opt in ('-s', '--suffix'): + suffixes.append(value) + elif opt in ('-f', '--file'): + inf = value + if inf == '-': inf = sys.stdin + + basenames = {} + if inf is None: + # Generer une liste de fichiers en ne gardant que les fichiers archive qui + # ont un numero de version. Classer ces fichiers par noms de base + if not argv: argv = ['*'] + for arg in argv: + if path.isdir(arg): arg += '/' + if not arg.endswith('*'): arg += '*' + + for p in glob(arg): + n = strip_suffixes(p, suffixes) + s = p[len(n):] + + if has_version(n): + b = strip_version(n) + s + if b not in basenames: + basenames[b] = [] + basenames[b].append(p) + else: + lines = Lines() + for p in lines.readlines(inf): + n = strip_suffixes(p, suffixes) + s = p[len(n):] + + if has_version(n): + b = strip_version(n) + s + if b not in basenames: + basenames[b] = [] + basenames[b].append(p) + + # Pour chacun des noms de base, classer la liste et retourner la(les) + # version(s) demandée(s) + pv = PathVersion(suffixes) + for basename, paths in basenames.items(): + if all: + paths.sort(pv.cmp, reverse=reverse) + for p in paths: + print p + else: + paths.sort(pv.cmp, reverse=True) + if reverse: + for p in paths[1:]: + print p + else: + print paths[0] + +if __name__ == '__main__': + run_plver() diff --git a/pyulib/src/uapps/pyucontacts.py b/pyulib/src/uapps/pyucontacts.py new file mode 100755 index 0000000..165102e --- /dev/null +++ b/pyulib/src/uapps/pyucontacts.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +u"""%(scriptname)s: afficher les contacts téléphoniques + +USAGE + %(scriptname)s [patterns...]""" + +import os, sys, re, string +from os import path + +from ulib.all import * + +class ContactsrcFile(ShConfigFile): + CONTACTSRC = "contactsrc" + DEFAULT_CONTACTSFILE = "Contacts.txt" + + def __init__(self, file=None, raise_exception=True): + if file is None: + testdir = path.join(scriptdir, "../../test") + utoolsrcdir = path.join(path.expanduser("~"), ".utools") + if path.isdir(testdir): file = path.join(testdir, self.CONTACTSRC) + else: file = path.join(utoolsrcdir, self.CONTACTSRC) + raise_exception = False + ShConfigFile.__init__(self, file=file, raise_exception=raise_exception) + + CONTACTSDIR = "contactsdir" + CONTACTSFILE = "contacts" + + def __p(self, p, refdir=None): + if refdir is not None: + if path.isfile(refdir): refdir = path.split(refdir)[0] + return abspath2(path.expanduser(p), refdir) + def __pcd(self, p): + return self.__p(p, self.get_contactsdir()) + + def __get(self, name, default, list=False, strip=False, fix_path=True): + if fix_path is True: fix_path = self.__pcd + if self.has_key(name): + if list: + values = self.get_list(name, strip) + if fix_path: values = [fix_path(value) for value in values] + value = values + else: + value = self[name] + if fix_path: value = fix_path(value) + else: + value = default + return value + + def __join(self, values): + if isseq(values): values = u"\n".join(map(lambda v: _u(v), values)) + else: values = _u(values) + return values + + def __set(self, name, value, list=False): + if value is None: + if self.has_key(name): del self[name] + else: + self[name] = self.__join(value) + + def get_contactsdir(self): + """Retourner le répertoire de base pour les fichiers de contact + Par défaut, il s'agit de ~/.utools + """ + return self.__get(self.CONTACTSDIR, self.absfiledir, list=True, strip=True, fix_path=self.__p) + def set_contactsdir(self, contactsdir=None): + self.__set(self.CONTACTSDIR, contactsdir, list=True) + + def get_default_contactsfile(self): + return self.__pcd(self.DEFAULT_CONTACTSFILE) + + def get_contactsfile(self, default=True): + """Retourner la valeur contactsfile si elle est définie, default sinon. + Il s'agit du fichier qui contient les tâches par défaut. + Si default==True, retourner la première valeur de get_stores(), sinon + la valeur par défaut ~/.utools/Contacts.txt + """ + if default is True: + default = self.get_default_contactsfile() + return self.__get(self.CONTACTSFILE, default) + def set_contactsfile(self, contactsfile): + self.__set(self.CONTACTSFILE, contactsfile) + + +def compile_pattern(pattern): + if isstr(pattern): pattern = re.compile(pattern) + return pattern + +TEL = u"Tél personnel" +FAX = u"Fax personnel" +GSM = u"GSM personnel" +EMAIL = u"E-mail" +ADRESSE = u"Adresse personnelle" +PROFTEL = u"Tél professionnel" +PROFFAX = u"Fax professionnel" +PROFGSM = u"GSM Professionnel" +PROFEMAIL = u"E-mail professionnel" +PROFADRESSE = u"Adresse professionnelle" + +class Telephone(object): + _tel = None + _prefix = None + + RE_SPACES = re.compile(r'\s+') + PREFIXES = {None: TEL, u"tel": TEL, u"fax": FAX, u"gsm": GSM, + u"ptel": PROFTEL, u"pfax": PROFFAX, u"pgsm": PROFGSM, + u"email": EMAIL, u"mail": EMAIL, + u"pemail": PROFEMAIL, u"pmail": PROFEMAIL, + u"adresse": ADRESSE, u"padresse": PROFADRESSE, + u"adr": ADRESSE, u"padr": PROFADRESSE, + } + LITERALS = [EMAIL, PROFEMAIL, ADRESSE, PROFADRESSE] + + def is_literal(cls, prefix): + prefix = cls.PREFIXES.get(prefix, prefix) + return prefix in cls.LITERALS + is_literal = classmethod(is_literal) + + def __init__(self, tel, prefix=None): + tel = self.RE_SPACES.sub(u"", tel) + prefix = self.PREFIXES.get(prefix, prefix) + self._tel = _u(tel) + self._prefix = _u(prefix) + + prefix = property(lambda self: self._prefix) + + def get_tel(self, cooked=True): + if not cooked or self._prefix in self.LITERALS: + return self._tel + else: + tel = self._tel + nums = [] + while tel: + if len(tel) > 4: + num = tel[-2:] + tel = tel[:-2] + nums.insert(0, num) + else: + nums.insert(0, tel) + tel = None + return u" ".join(nums) + tel = property(get_tel) + + def __str__(self): + s = _s(self.tel) + if self._prefix is not None: + s = "%s (%s)" % (s, _s(self._prefix)) + return s + def __repr__(self): + tel = self.tel + prefix = self._prefix + if prefix is None: prefix = "None" + else: prefix = "'%s'" % prefix + return "%s('%s', %s)" % (self.__class__.__name__, tel, prefix) + def __unicode__(self): + u = _u(self.tel) + if self._prefix is not None: + u = u"%s (%s)" % (u, _u(self._prefix)) + return u + + def matches(self, pattern): + return compile_pattern(pattern).search(self._tel) is not None + +class Contact(object): + _nom = None + _prenom = None + _tels = None + + RE_COMMA = re.compile(r'\s*,\s*') + RE_PREF = re.compile(r'(\S+)=') + RE_PREF_TEL = re.compile(r'(?:(\S+)=)?((?:\d|\s)+)$') + RE_PREF_ANY = re.compile(r'(?:(\S+)=)?\s*(.+)\s*$') + + def __init__(self, line): + parts = self.RE_COMMA.split(line.strip()) + self._nom = parts[0:1] and parts[0] or u"" + self._prenom = parts[1:2] and parts[1] or u"" + self._tels = [] + for part in filter(None, parts[2:]): + if not part: continue + mo = self.RE_PREF.match(part) + if mo is not None: + prefix = mo.group(1) + if Telephone.is_literal(prefix): + mo = self.RE_PREF_ANY.match(part) + if mo is None: raise ValueError("inconsistent match: %s" % part) + prefix, tel = mo.group(1), mo.group(2) + self._tels.append(Telephone(tel, prefix)) + continue + mo = self.RE_PREF_TEL.match(part) + if mo is None: + raise ValueError("Invalid telephone number: %s" % part) + prefix, tel = mo.group(1), mo.group(2) + self._tels.append(Telephone(tel, prefix)) + + nom = property(lambda self: self._nom) + prenom = property(lambda self: self._prenom) + tels = property(lambda self: self._tels) + + def get_np(self): + np = [] + if self._nom: np.append(self._nom) + if self._prenom: np.append(self._prenom) + return u" ".join(np) + np = property(get_np) + + def get_pn(self): + pn = [] + if self._prenom: pn.append(self._prenom) + if self._nom: pn.append(self._nom) + return u" ".join(pn) + pn = property(get_pn) + + def _tel(self, name, cooked=True): + name = Telephone.PREFIXES.get(name, name) + for tel in self.tels: + if tel.prefix == name: + return tel.get_tel(cooked) + return None + tel = property(lambda self: self._tel("tel")) + fax = property(lambda self: self._tel("fax")) + gsm = property(lambda self: self._tel("gsm")) + mail = property(lambda self: self._tel("mail")) + ptel = property(lambda self: self._tel("ptel")) + pfax = property(lambda self: self._tel("pfax")) + pgsm = property(lambda self: self._tel("pgsm")) + pmail = property(lambda self: self._tel("pmail")) + xtel = property(lambda self: self._tel("tel", False)) + xfax = property(lambda self: self._tel("fax", False)) + xgsm = property(lambda self: self._tel("gsm", False)) + xmail = property(lambda self: self._tel("mail", False)) + xptel = property(lambda self: self._tel("ptel", False)) + xpfax = property(lambda self: self._tel("pfax", False)) + xpgsm = property(lambda self: self._tel("pgsm", False)) + xpmail = property(lambda self: self._tel("pmail", False)) + + def matches(self, patterns): + if not isseq(patterns): patterns = (patterns,) + patterns = map(compile_pattern, patterns) + nom = self._nom + prenom = self._prenom + tels = self._tels + for pattern in patterns: + if pattern.search(nom) is None: + if pattern.search(prenom) is None: + for tel in tels: + if tel.matches(pattern): return True + return False + return True + +RE_COMMENT = re.compile(r'^#|^\s*$') +def load_contacts(infdn): + lines = Lines() + lines.readlines(infdn) + lines.filter(lambda line: RE_COMMENT.match(line) is None) + return [Contact(line) for line in lines] + +def show_contacts(contacts): + for contact in contacts: + etitle(contact.np) + for tel in contact.tels: + uprint(tel) + +def display_help(): + uprint(__doc__ % globals()) + +def run_Contacts(): + options, longoptions = build_options([ + ('h', 'help', "Afficher l'aide"), + ('o:', 'output=', "Exporter au format csv pour Outlook"), + ]) + options, args = get_args(None, options, longoptions) + action = "search" + outf = None + for option, value in options: + if option in ('-h', '--help'): + display_help() + sys.exit(0) + elif option in ('-o', '--output'): + action = "export" + outf = value + + cf = ContactsrcFile() + contacts = load_contacts(cf.get_contactsfile()) + if action == "search": + if args: + patterns = map(compile_pattern, map(_u, args)) + contacts = [contact for contact in contacts + if contact.matches(patterns)] + show_contacts(contacts) + elif action == "export": + lines = [] + lines.append(u'"Nom","Téléphone (domicile)","Télécopie (domicile)","Tél. mobile","Adresse de messagerie","Téléphone (bureau)","Télécopie (bureau)","Téléphone 2 (bureau)","Adresse de messagerie 2"\r\n') + for contact in contacts: + values = [contact.pn or u"", + contact.xtel or u"", + contact.xfax or u"", + contact.xgsm or u"", + contact.xmail or u"", + contact.xptel or u"", + contact.xpfax or u"", + contact.xpgsm or u"", + contact.xpmail or u"", + ] + if values[7]: + if not values[5]: + values[5] = values[7] + values[7] = u"" + elif not values[3]: + values[3] = values[7] + values[7] = u"" + + lines.append(u",".join(values) + u"\r\n") + cp1252 = UnicodeIO("cp1252") + lines = map(cp1252.s, lines) + + close = True + if outf != "-": + outf = open(outf, "wb") + else: + outf = sys.stdout + close = False + outf.writelines(lines) + if close: outf.close() + +if __name__ == '__main__': + run_Contacts() diff --git a/pyulib/src/uapps/pyurelease.py b/pyulib/src/uapps/pyurelease.py new file mode 100755 index 0000000..2be6d9c --- /dev/null +++ b/pyulib/src/uapps/pyurelease.py @@ -0,0 +1,811 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +u"""%(name)s - Gérer les releases d'un projet +USAGE + %(name)s [options] [projdir] +""" + +import os, sys, string, re, shutil +from os import path +from time import time, localtime + +from ulib.all import * +from ulib.vcs import * +from ulib.ext.tarfile import tarfile +from uapps.update_inc import update_inc_params, update_inc +from pyutools.TODO import TODOs +from pyutools.wo import wo_projname_and_projtype + +def uappspath(file): + """Exprimer un fichier par rapport au répertoire de uapps. + XXX il vaut mieux déplacer la logique dans un module, et exprimer le fichier + par rapport à la position du module + """ + uappsdir = path.join(sys.prefix, 'lib', 'python' + sys.version[:3], 'site-packages', 'uapps') + return path.join(uappsdir, file) + +################################################## +### Méthodes de gestion des paramètres + +# Note: les paramètres peuvent être donnés sur la ligne de commande, dans un +# fichier release.conf, et ont une valeur par défaut + +## On construit les structures au fur et à mesure +# Paramètres valides dans release.conf et méthodes associées +params_for_release_conf = {} + +# Valeurs par défauts +params_defaults = {} + +# Paramètres sur la ligne de commande +argsdesc = [ + # Afficher l'aide + ('h', 'help', u"Afficher l'aide"), + ] + +## Gestion des versions +uver = 'release_update_version' +sver = 'force_version' + +def set_uver(p, value=False): p[uver] = is_yes(value) +def set_sver(p, value): p[sver] = value + +params_for_release_conf.update({uver: set_uver}) +params_defaults.update({uver: False, sver: None}) +argsdesc.extend([ + ('n', 'keep-version', u"Ne pas changer la version (par défaut)"), + ('u', 'update-version', u"Mettre à jour la version"), + ('v:', 'set-version=', u"Spécifier une nouvelle version"), + ]) + +## suivi des changements +log = 'release_log_changes' +todorc = 'release_todorc' + +def set_log(p, value=False): p[log] = is_yes(value) +def set_todorc(p, value): p[todorc] = path.abspath(value) + +params_for_release_conf.update({log: set_log, todorc: set_todorc}) +params_defaults.update({log: True, todorc: None}) +argsdesc.extend([ + (None, 'log-changes', u"Saisir les changements pour une nouvelle version (par défaut)"), + (None, 'no-log-changes', u"Ne pas saisir les changements"), + (None, 'todorc=', u"Spécifier le fichier de configuration pour la gestion des TODOs"), + ]) + +## gestion des tags (avec CVS) +tag = 'release_tag_in_vcs' +stag = 'force_tag' + +def set_tag(p, value=False): p[tag] = is_yes(value) +def set_stag(p, value): p[stag] = value + +params_for_release_conf.update({tag: set_tag}) +params_defaults.update({tag: True, stag: None}) +argsdesc.extend([ + (None, 'tag', u"tagger les sources avec le nom du projet et la version (par défaut)"), + (None, 'no-tag', u"Ne pas tagger les sources"), + (None, 'set-tag', u"Spécifier le tag à utiliser"), + ]) + +## gestion des fichiers de licence +lic = 'release_include_license' +slic = 'force_license' + +def set_lic(p, value=False): p[lic] = is_yes(value) +def set_slic(p, value): p[slic] = value + +params_for_release_conf.update({lic: set_lic}) +params_defaults.update({lic: True, slic: None}) +argsdesc.extend([ + (None, 'include-license', u"Inclure un fichier de licence"), + (None, 'no-include-license', u"Ne pas inclure le fichier de licence"), + (None, 'set-license', u"Choisir le fichier de licence à utiliser"), + (None, 'ur', u"Spécifier la licence pour les produits de l'université de la Réunion"), + (None, 'jclain', u"Spécifier la licence pour les produits de Jephté CLAIN"), + ]) + +## création de l'archive +bp = 'release_base_path' +exn = 'release_exclude_names' +exp = 'release_exclude_paths' +an = 'archive_name' +asrc = 'archive_source' +sas = 'source_archive_suffix' +ab = 'archive_binary' +bas = 'binary_archive_suffix' +aj = 'archive_javadoc' +jdas = 'archive_suffix' + +def set_bp(p, value): p[bp] = value +def set_exn(p, value): + if not p.has_key(exn): p[exn] = [] + if ':' in value: + values = string.split(value, ':') + else: + values = [value] + for value in values: + list_set(p[exn], value) +def set_exp(p, value): + if not p.has_key(exp): p[exp] = [] + if ':' in value: + values = string.split(value, ':') + else: + values = [value] + for value in values: + list_set(p[exp], value) +def set_an(p, value): p[an] = value +def set_asrc(p, value=False): p[asrc] = is_yes(value) +def set_sas(p, value): p[sas] = value +def set_ab(p, value=False): p[ab] = is_yes(value) +def set_bas(p, value): p[bas] = value +def set_aj(p, value=False): p[aj] = is_yes(value) +def set_jdas(p, value): p[jdas] = value + +params_for_release_conf.update({bp: set_bp, exn: set_exn, exp: set_exp}) +params_defaults.update({bp: '@@projname@@', #'@@projname@@-@@projver@@', + exn: [], exp: [], an: None, + asrc: False, sas: '', + ab: False, bas: '', + aj: False, jdas: '-api' + }) +argsdesc.extend([ + ('c', 'archive-source', u"Créer l'archive du source"), + ('B', 'archive-binary', u"Créer l'archive du produit binaire"), + ('J', 'archive-javadoc', u"Créer l'archive de la javadoc"), + ('b:', 'base-path=', u"Dans l'archive générée, répertoire de base"), + ('x:', 'exclude-names=', u"Noms de fichiers à exclure de l'archive"), + ('X:', 'exclude-paths=', u"Répertoires à exclure de l'archive"), + ]) + +# ensembles nommés d'options +pyp = 'release_use_python_defaults' +zop = 'release_use_zope_defaults' +jawop = 'release_use_jawo_defaults' + +def set_pyp(p, value=False): + if is_yes(value): + p[pyp] = True + p[zop] = p[jawop] = False + set_tag(p, True) + set_log(p, True) + set_lic(p, True) + set_exn(p, 'CVS/') + set_exn(p, '.svn/') + set_exn(p, '*.pyc') + set_exn(p, '*~') + set_exn(p, '.DS_Store') +def set_zop(p, value=False): + if is_yes(value): + set_pyp(True) + p[zop] = True + p[pyp] = p[jawop] = False + p[bp] = 'lib/python/Products/@@projname@@' #'lib/python/Products/@@projname@@-@@projver@@' +def set_jawop(p, value=False): + if is_yes(value): + p[jawop] = True + p[pyp] = p[zop] = False + set_tag(p, True) + set_log(p, True) + set_lic(p, True) + set_sas(p, '-src') + set_exn(p, 'CVS/') + set_exn(p, '.svn/') + set_exn(p, '*~') + set_exn(p, '.DS_Store') + set_exp(p, 'build/') + set_exp(p, '*.pbproj/*.pbxuser') + +params_for_release_conf.update({pyp: set_pyp, zop: set_zop, jawop: set_jawop}) +params_defaults.update({pyp: False, zop: False, jawop: False}) +argsdesc.extend([ + (None, 'auto', u"Autodétecter le type de projet (par défaut)"), + (None, 'no-auto', u"Ne pas autodétecter le type de projet"), + ('P', 'python-product', u"Choisir les options par défaut pour un projet Python"), + ('Z', 'zope-product', u"Choisir les options par défaut pour un projet Zope"), + ('W', 'jawo-product', u"Choisir les options par défaut pour un projet jabuild/wobuild"), + ]) + +def is_auto(opt): return opt in ('--auto', ) +def isnot_auto(opt): return opt in ('--no-auto', '-P', '--python-product', + '-Z', '--zope-product', + '-W', '--jawo-product', + ) + +# support de update_inc +ui = 'update_inc' + +def set_ui(p, value=False, basedir=None): + p[ui] = None + if is_yes(value): + if basedir is None: basedir = os.getcwd() + cf = ShConfigFile(path.join(basedir, '.uinst.conf'), raise_exception=False) + if cf.is_valid(): + # on force le mode quiet avec -q en attendant que update_inc supporte set_verbosity() + options = split_args(cf.get('update_inc_options', '') + ' -q') + split_args(cf.get('update_inc_args', '')) + p_ui, args_ui = update_inc_params(options, basedir=basedir) + p[ui] = (p_ui, args_ui) +def fold_maybe(p, vcsdir=None): + if p[ui]: + pr, args = p[ui] + if vcsdir is None: + update_inc(pr, args, 'fold') + return True + else: return vcsdir.fold(pr, args, 'fold') +def unfold_maybe(p, vcsdir=None): + if p[ui]: + pr, args = p[ui] + if vcsdir is None: update_inc(pr, args, 'unfold') + else: vcsdir.unfold(pr, args, 'unfold') + +params_for_release_conf.update({ui: set_ui}) +params_defaults.update({ui: False}) +argsdesc.extend([ + (None, 'update_inc', u"Activer le support de update_inc"), + ]) + +# autres paramètres qui sont utilisés par release() +cver = 'version_txt_created?' +clog = 'changes_txt_created?' +clic = 'license_txt_created?' +cd = 'curdir' +pn = 'projname' +pt = 'projtype' + +def set_cver(p, value=False): p[cver] = is_yes(value) +def set_clog(p, value=False): p[clog] = is_yes(value) +def set_clic(p, value=False): p[clic] = is_yes(value) +def set_cd(p, value): p[cd] = value +def set_pn(p, value): p[pn] = value +def set_pt(p, value): p[pt] = value + +params_for_release_conf.update({}) + +params_defaults.update({cver: False,clog: False, clic: False, cd: None, + pn: None, pt: None, + }) +argsdesc.extend([]) + +################################################## +# Méthodes utilitaires +def list_set(list, value): + """insérer une valeur dans une liste si elle n'y est pas déjà + """ + if value not in list: + list.append(value) + +def strip_release_from_version(v): + """Supprimer la date de release dd/mm/yyyy d'un numéro de version + """ + v = string.strip(v) + mo = re.match(r'(.*)-r[0-9]+/[0-9]+/[0-9]+$', v) + if mo is not None: + v = mo.group(1) + return v + +def _inc_last(v): + v0, v1 = re.match(r'^(.*?)([0-9]*)$', v).groups() + v1 = str(int(v1 or 0) + 1) + return v0 + v1 + +def increment_version(v): + """Incrémenter le dernier élément d'un numéro de version, et retourner les + différentes propositions possibles. + """ + vs = [] + # [0]: ajouter un chiffre + vs.append(v + '.1') + # [1]: incrémenter le dernier + vs.append(_inc_last(v)) + # [2]: incrémenter l'avant dernier + if v.count('.') > 1: + pos = v.rfind('.') + vs.append(_inc_last(v[:pos])) + + return vs + +def expand_projname_and_version(s, p): + s = string.replace(s, "@@projname@@", p[pn]) + s = string.replace(s, "@@projver@@", p[sver]) + return s + +################################################## +# Méthodes pour la création d'une archive +def tarpath(p): + """normaliser un chemin pour utilisation dans un fichier tar: il ne doit pas + se terminer par /, les séparateurs doivent être / et non \ et le répertoire + courant '.' est remplacé par '' + """ + p = path.normpath(p) + p = string.replace(p, '\\', '/') + while p[-1:] == '/': p = p[:-1] + if p == '.': p = '' + return p + +def normalize_paths(paths): + """Normaliser tous les chemins de la liste paths. + + Retourner une liste de tuples (tpath, apath), où tpath est le chemin + normalisé pour le fichier tar (séparateurs '/'), et apath est le chemin + absolu sur le système de fichier. + """ + return map(lambda p: (tarpath(p), path.abspath(p)), paths) + +def select_files(p, exclude_names=(), exclude_paths=(), basepath=None): + """Construire récursivement la liste de tous les fichiers à partir du + répertoire p, en excluant les répertoires dont le nom est donné dans + excludes_names, ou dont le chemin relativement à p est donné dans + exclude_paths + + on ne suit pas les liens symboliques. + + Note: on exclue automatiquement les fichiers qui sont 'binaires' + """ + if basepath is None: + return select_files(p, exclude_names, exclude_paths, p) + else: + files = [] + for file in os.listdir(p): + pf = path.join(p, file) # chemin absolu + relpf = pf[len(basepath) + 1:] # chemin relativement à basepath. +1 pour le '/' + + ignore = 0 + for pattern in exclude_names: + if matches_name(pattern, file, p): + ignore = 1 + else: + for pattern in exclude_paths: + if matches_name(pattern, relpf, basepath): + ignore = 1 + if not ignore: + files.append(pf) + if path.isdir(pf) and not path.islink(pf): + files.extend(select_files(pf, exclude_names, exclude_paths, basepath)) + return files + +def create_archive(p, paths, altbp=None): + """Créer l'archive p[an].tgz dans le répertoire courant avec les + répertoires énumérés dans paths + """ + archive = tarfile.open(p[an] + '.tgz', 'w:gz') + archive.posix = 0 + + if altbp is None: altbp = p[bp] + basepath = altbp and altbp + '/' or '' + for tp, ap in paths: + tp = tp and tp + '/' or '' + files = select_files(ap, p[exn], p[exp]) + for file in files: + tfile = basepath + tp + string.replace(file[len(ap) + 1:], '\\', '/') + archive.add(file, tfile, False) + + archive.close() + +################################################## +# L'application release + +def print_help(name=None): + if name is None: + name = path.split(sys.argv[0])[1] + print __doc__ % vars() + print "OPTIONS" + for argdesc in argsdesc: + so = argdesc[0:1] and argdesc[0] or None + if so is not None: so = '-' + so[:1] + + lo = argdesc[1:2] and argdesc[1] or None + if lo is not None: lo = (so is not None and ', ' or '') + '--' + lo + + desc = argdesc[2:3] and argdesc[2] or None + + if so is not None or lo is not None: + print " %s%s" % (so or '', lo or '') + if desc is not None: print " %s" % desc + +def release(p, *paths): + release_date = time() + + # Obtenir, vérifier et/ou normaliser les chemins + paths = list(paths) + + curdir = p[cd] + if curdir is None: die(u"curdir doit être spécifié") + projname = p[pn] + if projname is None: die(u"Il faut donner le nom du projet") + projtype = p[pt] + + tp0, ap0 = paths[0] + + ### obtenir le numéro de version courant et incrémenter le numéro de version + maj_version = False # True si la version a été changée + + # calculer le nom du fichier qui contient la version + version_txt = 'VERSION.txt' + if not path.isfile(path.join(ap0, version_txt)): + if path.isfile(path.join(ap0, 'version.txt')): + version_txt = 'version.txt' + aversion_txt = path.join(ap0, version_txt) + + # lire la version qui se trouve dans le fichier + if not path.isfile(aversion_txt): + if not ask_yesquit(u"Le fichier %s n'existe pas. Faut-il le créer?" % version_txt, True, minlevel=I_INTER): + raise Exit, 1 + set_cver(p, True) + + vf = TextFile(aversion_txt, raise_exception=False) + if vf.lines: + v = strip_release_from_version(vf.lines[0]) + einfo(u"La version actuelle est %s" % v) + else: + v = '' + if p[uver]: + v = '0.0' + einfo(u"Pas d'information de version. on commence à 0.1") + elif p[sver] is not None: + einfo(u"Pas d'information de version. On commence à %s (forcé)" % p[sver]) + else: + einfo(u"Pas d'information de version.") + + vs = None + if p[sver] is None: + if p[uver]: + vs = increment_version(v) + v = vs[1] + maj_version = True + p[sver] = v + else: + maj_version = p[sver] != v + + if maj_version: + if vs is None: + if not ask_yesquit(u"Mettre à jour la version vers %s?" % p[sver], True): + raise Exit, 1 + else: + if check_verbosity(I_NORMAL): + for i in range(len(vs)): + einfo(u"%i - Passer à la version %s" % (i, vs[i])) + r = read_value(u"Mettre à jour la version vers %s? [Oq]" % p[sver], default="O", refuse_empty=False) + v = None + if re.match(r'\d+$', r) is not None: + i = int(r) + if i >= 0 and i < len(vs): + v = vs[i] + elif is_yes(r): + v = vs[1] + + if v is None: raise Exit, 1 + else: p[sver] = v + + vf.lines[0:1] = [p[sver] + '-r' + datef(FR_DATEF, release_date)] + vf.writelines() + + # mettre à jour p[bp] qui peut contenir des tags @@projname@@ et @@projver@@ + p[bp] = expand_projname_and_version(tarpath(p[bp]), p) + + ### enregistrer les changements, seulement si on met à jour la version + if p[log] and maj_version: + if not ask_yesquit(u"Saisir le changelog pour la version %s?" % p[sver], True, minlevel=I_INTER): + raise Exit, 1 + + template = u""" +-EDIT-: ---------------------------------------------------------------------- +-EDIT-: Saisissez une description de la distribution %(projname)s-%(version)s +-EDIT-: +-EDIT-: Les lignes commencant par '-EDIT-:' sont automatiquement supprimees +-EDIT-: dans le fichier de sortie (%(filename)s) +-EDIT-: +-EDIT-: ---------------------------------------------------------------------- +""" + project = path.split(ap0)[1] + # Calculer automatiquement les tâches qui doivent être incluses dans le log + ts = TODOs(p[todorc]) + ts.purge(project) + released = ts.list_releaseable(project) + + mark_released = False + if released: + mark_released = True + template = template + u"""\ +-EDIT-: Pour information, les informations suivantes seront automatiquement +-EDIT-: ajoutées à la description de la distribution: +-EDIT-: +""" + lines = [] + for ds in map(lambda d: d.to_string(), released): + ds = ds.replace("%", "%%") + lines.append("\n".join(map(lambda l: "-EDIT-: " + l, string.split(ds, "\n")))) + template = template + "\n-EDIT-: ----\n".join(lines) + + todofile = ts.get_todofile(project) + if todofile is not None and todofile.can_release(): + # éditer le fichier + change_lines = edit_template(template % {'projname': projname, + 'version': p[sver], + 'filename': todofile.get_file()}, + '-EDIT-:') + todofile.release(released, release_date, p[sver], change_lines.join()) + todofile.save() + + else: + # calculer le nom du fichier qui contient les changements + changes_txt = 'CHANGES.txt' + if not path.exists(path.join(ap0, changes_txt)): + if path.exists(path.join(ap0, 'changes.txt')): + changes_txt = changes.txt + achanges_txt = path.join(ap0, changes_txt) + + if not path.exists(achanges_txt): + set_clog(p, True) + + # éditer le fichier + change_lines = edit_template(template % {'projname': projname, + 'version': p[sver], + 'filename': changes_txt}, + '-EDIT-:') + if released: + first = False + if change_lines: change_lines.append("") + else: first = True + for todo in released: + if first: first = False + else: change_lines.append("----") + change_lines.extend(string.split(todo.to_string(), "\n")) + + # ajouter le changelog + cf = TextFile(achanges_txt, raise_exception=False) + + if cf.lines and string.strip(cf.lines[-1]): + cf.lines.append("") + title = u"%s %s-%s" % (datef(FRHM_DATEF, release_date), projname, p[sver]) + cf.lines[0:0] = [title, "=" * len(title), ""] + change_lines + (cf.lines and [""] or []) + + cf.writelines() + + ts.mark_released(project) + + ### ajouter le fichier de licence + alicense_txt = None + if p[lic] and maj_version: + # calculer le nom du fichier qui contient la licence + license_txt = 'LICENSE.txt' + if not path.exists(path.join(ap0, license_txt)): + if path.exists(path.join(ap0, 'license.txt')): + license_txt = 'license.txt' + alicense_txt = path.join(ap0, license_txt) + + if not path.exists(alicense_txt): + set_clic(p, True) + + # si on a spécifié un fichier de license, forcer l'écrasement. Sinon, on + # ne copie la license que si elle n'existe pas déjà + copy_license = False + if p[slic] is None: + if p[clic]: + copy_license = True + p[slic] = uappspath('ur_license.txt') # par défaut + if p[slic] is not None: + copy_license = True + if dirname(p[slic]) == '': + # si on donne juste le nom du fichier, et qu'il n'existe pas + # dans le répertoire courant, l'exprimer par rapport à au + # répertoire du module uapps. XXX cf la documentation de + # uappspath() pour voir ce qu'il faut corriger + if not path.isfile(path.join(curdir, p[slic])): + p[slic] = tmp = uappspath(p[slic]) + if not path.isfile(tmp): + tmp = tmp + '_license.txt' + if path.exists(tmp): p[slic] = tmp + ## si on donne juste le nom du fichier, et qu'il n'existe pas + ## dans le répertoire courant, l'exprimer par rapport à + ## au répertoire de ce module + #if not path.isfile(path.join(curdir, p[slic])): + # p[slic] = path.join(dirname(__file__), p[slic]) + # if not path.isfile(p[slic]): + # tmp = p[slic] + '_license.txt' + # if path.exists(tmp): + # p[slic] = tmp + if not path.isfile(p[slic]): + die(u"Fichier de licence inexistant: %s" % p[slic]) + + if copy_license: + if not ask_yesquit(u"Copier le fichier de licence %s?" % basename(p[slic]), True, minlevel=I_INTER): + raise Exit, 1 + + if not path.exists(alicense_txt) or not path.samefile(p[slic], alicense_txt): + shutil.copy(p[slic], alicense_txt) + p[slic] = alicense_txt + + lf = TextFile(p[slic]) + if lf.grep(r'.*(@@projname@@|@@year@@)'): + lf.replace('@@projname@@', projname) + lf.replace('@@year@@', datef(YEAR_DATEF)) + lf.writelines() + + ### tag des fichiers si VCS est activé et si la version a été modifiée + if p[tag] and maj_version and is_vcsdir(ap0): + vcsdir = get_vcsdir(ap0) + vcsdir.set_tag_rootdir(False) + options = dict(trace=True, quiet=True, auto_update=False) + + # déterminer le nom du tag + if p[stag] is None: + p[stag] = '%s-%s' % (projname, string.replace(p[sver], '.', '-')) + + if ask_yesno(u"Taguer avec %s?" % p[stag], True, minlevel=I_INTER): + os.chdir(ap0) + + # avant vcs, il faut faire un fold si on supporte update_inc + estep(u"Suppression des inclusions") + folded = fold_maybe(p, vcsdir) + + estep(u"Marquage des fichiers avec le tag %s" % p[stag]) + if p[cver]: vcsdir.add(version_txt, **options) + if p[clog]: vcsdir.add(changes_txt, **options) + if p[clic]: vcsdir.add(license_txt, **options) + + message = u"Création de la distribution pour le tag %s" % p[stag] + vcsdir.commit(message, **options) + vcsdir.tag(p[stag], message, **options) + + # après vcs, il faut faire un unfold si on supporte update_inc + if folded: + estep(u"Mise à jour des inclusions") + unfold_maybe(p, vcsdir) + + ### Créer la distribution si cela a été demandé + # vérifier la cohérence de certains paramètres + if p[asrc] or p[ab] or p[aj]: + if (p[ab] or p[aj]) and not p[jawop]: + ewarn(u"La création d'une archive binaire/javadoc n'est supportée que pour les projets WebObjects") + + # avant de créer l'archive, unfold si on supporte update_inc + unfold_maybe(p) + + # contruire l'archive du source + p[an] = '%s-%s%s' % (projname, p[sver], p[sas]) + if ask_yesno(u"Créer l'archive %s.tgz?" % p[an], True, minlevel=I_INTER): + if p[bp] != '': + # si on a spécifié un chemin de base, inclure la racine de façon relative + paths[0] = ('', paths[0][1]) + estep(u"Création de l'archive %s.tgz" % p[an]) + create_archive(p, paths) + + # construire l'archive du binaire + if p[ab] and p[jawop]: + archive_created = False + for suffix in ('', '.woa', '.framework'): + projdir = projname + suffix + p[an] = '%s-%s%s' % (projdir, p[sver], p[bas]) + aprojdir = path.join(ap0, 'build', projdir) + if path.isdir(aprojdir): + if ask_yesno(u"Créer l'archive des binaires %s.tgz?" % p[an], True, minlevel=I_INTER): + estep(u"Création de l'archive %s.tgz" % p[an]) + create_archive(p, [(projdir, aprojdir)], '') + archive_created = True + if not archive_created: + eerror(u"Imposssible de créer l'archive binaire: il faut construire le projet d'abord") + + # construire l'archive de la documentation + if p[aj] and p[jawop]: + p[an] = '%s-%s%s' % (projname, p[sver], p[jdas]) + aprojdir = path.join(ap0, 'build', 'api') + if path.isdir(aprojdir): + if ask_yesno(u"Créer l'archive de la documentation %s.tgz?" % p[an], True, minlevel=I_INTER): + estep(u"Création de l'archive %s.tgz" % p[an]) + create_archive(p, [(projdir, aprojdir)]) + else: + eerror(u"Répertoire de la javadoc introuvable. L'archive n'a pas été générée") + +def run_release(): + ### Lire les arguments + options, longoptions = build_options(argsdesc) + opts, paths = get_args(None, options, longoptions) + p = {} + + # todorc est traité à part, parce qu'il peut s'agir d'un répertoire relatif + index = 0 + for opt, value in opts: + if opt in ('--todorc', ): + set_todorc(p, value) + del opts[index] + break + index = index + 1 + + ### Vérifier et normaliser les chemins + if not paths: paths = ['.'] + paths = normalize_paths(paths) + tp0, ap0 = paths[0] + + curdir = os.getcwd() + if curdir[:len(ap0)] == ap0: + # nous sommes dans le répertoire de la distribution, se placer au-dessus + os.chdir(path.split(ap0)[0]) + p[cd] = curdir + + ### Lecture des paramètres + # Si nécessaire, auto-détecter le type de projet + # ne pas autodétecter s'il existe un fichier .uinst.conf + rcf = ShConfigFile(path.join(ap0, '.uinst.conf'), raise_exception=False) + + projname = projtype = None + projecttype_autodetected = False + autodetect_projecttype = not rcf.is_valid() + for opt, value in opts: + if is_auto(opt): autodetect_projecttype = True + elif isnot_auto(opt): autodetect_projecttype = False + + if autodetect_projecttype: + projname, projtype = wo_projname_and_projtype(ap0) + if projtype in ('woapplication', 'woframework'): + projecttype_autodetected = True + set_jawop(p, True) + + if projname is None: + # sinon, le nom de base de l'archive est le nom du répertoire + projname = path.split(ap0)[1] + p[pn] = projname + p[pt] = projtype + + # Tout d'abord, essayer de lire les paramètres dans un fichier .uinst.conf + if rcf.is_valid(): + for key, set_func in params_for_release_conf.items(): + if not rcf.has_key(key): continue + + value = rcf[key] + if key == ui: + # cas particulier: ui demande le répertoire de base + set_func(p, value, ap0) + else: + set_func(p, value) + + # puis lire les paramètres en ligne de commande + for opt, value in opts: + if opt in ('-h', '--help'): + print_help() + sys.exit(0) + + elif opt in ('-n', '--keep-version'): set_uver(p, False) + elif opt in ('-u', '--update-version'): set_uver(p, True) + elif opt in ('-v', '--set-version'): set_sver(p, value) + + elif opt in ('--log-changes', ): set_log(p, True) + elif opt in ('--no-log-changes', ): set_log(p, False) + + elif opt in ('--tag', ): set_tag(p, True) + elif opt in ('--no-tag', ): set_tag(p, False) + elif opt in ('--set-tag', ): set_stag(p, value) + + elif opt in ('--include-license', ): set_lic(p, True) + elif opt in ('--no-include-license', ): set_lic(p, False) + elif opt in ('--set-license', ): set_slic(p, value) + elif opt in ('--ur', ): set_slic(p, 'ur_license.txt') + elif opt in ('--jclain', ): set_slic(p, 'jclain_license.txt') + + elif opt in ('-c', '--archive-source'): set_asrc(p, True) + elif opt in ('-B', '--archive-binary'): set_ab(p, True) + elif opt in ('-J', '--archive-javadoc'): set_aj(p, True) + elif opt in ('-b', '--base-path'): set_bp(p, value) + elif opt in ('-x', '--exclude-names'): set_exn(p, value) + elif opt in ('-X', '--exclude-paths'): set_exp(p, value) + + elif opt in ('-P', '--python-product'): set_pyp(p, True) + elif opt in ('-Z', '--zope-product'): set_zop(p, True) + elif opt in ('-W', '--jawo-product'): set_jawop(p, True) + + elif opt in ('--update_inc', ): set_ui(p, True, ap0) + + # compléter avec les valeurs par défaut des paramètres + for key, value in params_defaults.items(): + if not p.has_key(key): + p[key] = value + + # lancer l'application + try: + exitcode = apply(release, tuple([p] + paths)) + except Exit, exitcode: + pass + sys.exit(exitcode) + +if __name__ == '__main__': + run_release() diff --git a/pyulib/src/uapps/pyutasks.py b/pyulib/src/uapps/pyutasks.py new file mode 100755 index 0000000..9744d7d --- /dev/null +++ b/pyulib/src/uapps/pyutasks.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +import sys + +from ulib.all import * +from ulib.tasks import * +from uapps.tasks import * + +def display_help(): + uprint(u"""%(scriptname)s: gestion de tâches + +USAGE + %(scriptname)s [options] cmd [args] + +OPTIONS + -h, --help + -c, --config + Spécifier le fichier de configuration (par défaut ~/.utools/tasksrc) + -S, --stores + Spécifier la liste des sources pour les tâches (une par ligne). La + commande add utilise la première source de cette liste. Toutes les + autres commandes agissent sur l'ensemble des sources. + -s, --tasksfile + Spécifier une source unique pour toutes les opérations. + --tasksdir + --projdirs + --purgedfile + Autres options qui peuvent être modifiées dans le fichier de + configuration. +COMMANDS + add [-d] [-e] title [+projects...] [@contexts...] + Créer une tâche. + -d marquer la nouvelle tâche comme faite. + -e éditer la nouvelle tâche. + list [-aqdln] [filter...] + Lister les titres des tâches correspondant au filtre. + -a Afficher TOUTES les tâches (par défaut si un filtre est fourni) + -q Afficher les tâches qui sont affichable (par défaut) + -d Afficher uniquement les tâches qui peuvent être effectuées + -l Afficher aussi les détails sur les tâches. + -n Ne pas afficher les statistiques. + edit tid + Editer la tâche #tid. + do tid [tids...] + Marquer la(es) tâche(s) #tid(s) comme faite(s). + nodo tid [tids...] + Marquer la(es) tâche(s) planifiées #tid(s) comme NON faite(s). + La prochaine échéance de la tâche est calculée. + start [-x] tid [summary] + Démarrer une tâche: la marquer comme étant en cours, en ajoutant + éventuellement une description de ce qui doit être fait. + -x Arrêter toutes les tâches en cours avant de démarrer la tâche + sélectionnée. + stop tid [summary] + Arrêter une tâche, en ajoutant éventuellement une description de ce qui + a été fait. + stop -a + Arrêter TOUTES les tâches en cours, plutôt que simplement celle + sélectionnée. + toggle [-x] tid [summary] + Démarrer/arrêter une tâche. + -x S'il faut démarrer la tache, arrêter d'abord toutes les autres + tâches. Sinon, ignorer cette option. + remove tid [tids...] + Supprimer la(es) tâche(s) #tid(s). + purge + Archiver les tâches faites. Elles ne seront plus affichées. + report + Faire un état de l'état d'avancement des tâches par projet.""" % globals()) + +def run_tasks(): + options, longoptions = build_options([ + ('h', 'help', u"Afficher l'aide"), + ]) + ctl = TasksCtl() + options, args = ctl.get_args(None, options, longoptions) + for option, value in options: + if option in ('-h', '--help'): + display_help() + sys.exit(0) + elif ctl.is_option(option, value): + pass + + itf, cmd_name, args = get_tasks_itf(args, ctl) + if itf is None: + display_help() + sys.exit(1) + + try: + exit(itf.run(args, cmd_name=cmd_name)) + except Exit: raise + except: die() + +if __name__ == '__main__': + run_tasks() diff --git a/pyulib/src/uapps/pyuupdate_inc.py b/pyulib/src/uapps/pyuupdate_inc.py new file mode 100755 index 0000000..766433b --- /dev/null +++ b/pyulib/src/uapps/pyuupdate_inc.py @@ -0,0 +1,715 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +import os, sys, re, string +from os import path + +from ulib.all import * + +################################################## +# fonctions diverses + +verbosity = 1 +verbose = 2 +normal = 1 +quiet = 0 + +def _print(level, text, min_verbosity=normal): + """afficher un texte avec un niveau d'indentation, si le niveau de verbosité + est correct. + + Retourner 1 si le texte a été affiché, 0 sinon + """ + global verbosity + if verbosity < min_verbosity: return 0 + + if level < 0: level = 0 + print "%s%s" % (" " * level, text) + return 1 + +debug = 0 + +def _debug(text): + if debug: print text + +def select_files(p, exclude_names=(), exclude_paths=(), basepath=None): + """Construire récursivement la liste de tous les fichiers à partir du + répertoire p, en excluant les répertoires dont le nom est donné dans + excludes_names, ou dont le chemin relativement à p est donné dans + exclude_paths + + Note: on exclue automatiquement les fichiers qui sont 'binaires' + """ + if basepath is None: + return select_files(p, exclude_names, exclude_paths, p) + else: + files = [] + for file in os.listdir(p): + if fileext_is_binary(file): continue + + pf = path.join(p, file) # chemin absolu + relpf = pf[len(basepath) + 1:] # chemin relativement à basepath. +1 pour le '/' + + ignore = 0 + for pattern in exclude_names: + if matches_name(pattern, file, p): + ignore = 1 + else: + for pattern in exclude_paths: + if matches_name(pattern, relpf, basepath): + ignore = 1 + if not ignore: + if path.isdir(pf): + files.extend(select_files(pf, exclude_names, exclude_paths, basepath)) + else: + files.append(pf) + return files + +################################################## +# Gestion des répertoires d'inclusion + +incpaths = None + +def init_incpaths(ignore_env=0): + """Initialiser incpaths, la liste globale des répertoires dans lesquels on + cherche les fichiers d'inclusions. + + Lire la valeur de la variable d'environnement UPDATEINCPATH, sauf si on est + dans un répertoire non déployé. + + Si on est dans un répertoire non déployé, s'assurer que le répertoire qui + contient ce script est dans incpaths. + """ + global incpaths + if incpaths is None: incpaths = [] + + if path.isdir(path.join(scriptdir, "test")): + # ignorer UPDATEINCPATH si on est dans le répertoire de test + ignore_env = 1 + + if not ignore_env: + incpaths = filter(None, string.split(os.environ.get('UPDATEINCPATH', ''), ':')) + else: + if scriptdir not in incpaths: + incpaths.insert(0, scriptdir) + _debug("init_incpaths()\n incpaths=\n %s" % string.join(incpaths, "\n ")) + +def set_incpaths(p): + """Soit un répertoire ou une liste de répertoires séparés par ':' + + Si p est un répertoire, l'ajouter à la liste des répertoires de recherche. + Si p est une liste de répertoires, remplacer la liste actuelle par cette + nouvelle liste. + """ + global incpaths + if incpaths is None: init_incpaths() + + if ':' in p: + incpaths[:] = map(path.abspath, filter(None, string.split(p, ':'))) + else: + p = path.abspath(p) + if p not in incpaths: + incpaths.append(p) + _debug("set_incpaths()\n incpaths=\n %s" % string.join(incpaths, "\n ")) + +def inc_abspath(file, local_incpaths=()): + """Retourner le chemin absolu vers un fichier. + + Si file est un chemin relatif, il est recherché dans incpaths et dans + local_incpaths + """ + file = path.expanduser(path.expandvars(file)) + + if path.isabs(file): return file + if path.exists(file): return path.abspath(file) + for incpath in incpaths: + pf = path.join(incpath, file) + if path.exists(pf): return pf + else: + for incpath in local_incpaths: + pf = path.join(incpath, file) + if path.exists(pf): return pf + return path.abspath(file) + +################################################## +# Gestion des lignes d'inclusion + +class InterestingLine: + def __init__(self, c=None): + """Initialiser l'objet + + re_inc matche une ligne d'inclusion repliée + + re_start_inc matche une ligne d'inclusion dépliée de type 'start' + + re_end_inc match une ligne d'inclusion dépliée de type 'end' + + re_require match une ligne d'inclusion de type 'require' + """ + self.re_comments = [ + re.compile(r'[ \t]*(?:r|R)(?:e|E)(?:m|M)'), + re.compile(r'[ \t]*##'), + re.compile(r'[ \t]*;;'), + re.compile(r'[ \t]*//'), + re.compile(r"[ \t]*''"), + ] + if c is None: + self.re_inc = re.compile(r'(.*)@include[ \t]+(.+)') + self.re_start_inc = re.compile(r'(.*)@inc\[(.+)') + self.re_end_inc = re.compile(r'(.*)@inc\](.+)') + self.re_require = re.compile(r'(.*)@require[ \t]+(.+)') + self.re_provide = re.compile(r'(.*)@provide[ \t]+(.+)') + self.c = '@' + elif c in ('*', '@'): + self.re_inc = re.compile(r'(.*)(?:@|\*)include[ \t]+(.+)') + self.re_start_inc = re.compile(r'(.*)(?:@|\*)inc\[(.+)') + self.re_end_inc = re.compile(r'(.*)(?:@|\*)inc\](.+)') + self.re_require = re.compile(r'(.*)(?:@|\*)require[ \t]+(.+)') + self.re_provide = re.compile(r'(.*)(?:@|\*)provide[ \t]+(.+)') + self.c = c + + self.inc_templ = "%s%sinclude %s" + self.start_inc_templ = "%s%sinc[%s" + self.end_inc_templ = "%s%sinc]%s" + self.require_templ = "%s%srequire %s" + self.provide_templ = "%s%sprovide %s" + + self.top_level = True + + def copy(self): + """faire une copie de cet objet + """ + il = self.__class__(self.c) + il.top_level = False + return il + + def is_comment(self, s): + """retourner vrai si s est un commentaire (matche l'une des expressions + régulières de re_comment) + """ + for re_comment in self.re_comments: + if re_comment.match(s): + return True + return False + + def matches(self, line): + """retourner vrai si line est une ligne 'intéressante': ligne + d'inclusion repliée, dépliée de type 'start', dépliée de type 'end', de + type 'require', ou de type 'provide' + + initialiser les informations sur cette ligne: mo, spaces, file + """ + self.__is_inc = 0 + self.__is_start_inc = 0 + self.__is_end_inc = 0 + self.__is_require = 0 + self.__is_provide = 0 + + mo = self.re_inc.match(line) + if mo is not None: + self.__is_inc = 1 + else: + mo = self.re_start_inc.match(line) + if mo is not None: + self.__is_start_inc = 1 + else: + mo = self.re_end_inc.match(line) + if mo is not None: + self.__is_end_inc = 1 + else: + mo = self.re_require.match(line) + if mo is not None: + self.__is_require = 1 + else: + mo = self.re_provide.match(line) + if mo is not None: + self.__is_provide = 1 + if mo is not None: + before = mo.group(1) + if self.is_comment(before): + self.before = before + self.file = mo.group(2) + self.pf = inc_abspath(self.file) + else: + self.__is_inc = 0 + self.__is_start_inc = 0 + self.__is_end_inc = 0 + self.__is_require = 0 + self.__is_provide = 0 + mo = None + self.mo = mo + + return mo is not None + + def is_inc(self, line=None): + """Si line==None, retourner vrai si la ligne matchée par matches() est + une ligne d'inclusion repliée. + + si line!=None, retourner vrai si la ligne est une ligne d'inclusion + repliée dont le nom de fichier correspond à la ligne matchée par + matches() + """ + if line is None: + return self.__is_inc + else: + mo = self.re_inc.match(line) + if mo is not None: + if mo.group(2) == self.file: + return 1 + return 0 + + def inc(self): + """retourner une ligne d'inclusion repliée construite avec self.before + et self.file + """ + return self.inc_templ % (self.before, self.c, self.file) + + def is_start_inc(self, line=None): + """Si line==None, retourner vrai si la ligne matchée par matches() est + une ligne d'inclusion dépliée de type 'start'. + + si line!=None, retourner vrai si la ligne est une ligne d'inclusion + dépliée de type 'start' dont le nom de fichier correspond à la ligne + matchée par matches() + """ + if line is None: + return self.__is_start_inc + else: + mo = self.re_start_inc.match(line) + if mo is not None: + if mo.group(2) == self.file: + return 1 + return 0 + + def start_inc(self): + """retourner une ligne d'inclusion dépliée de type 'start' construite + avec self.before et self.file + """ + return self.start_inc_templ % (self.before, self.c, self.file) + + def is_end_inc(self, line=None): + """Si line==None, retourner vrai si la ligne matchée par matches() est + une ligne d'inclusion dépliée de type 'end'. + + si line!=None, retourner vrai si la ligne est une ligne d'inclusion + dépliée de type 'end' dont le nom de fichier correspond à la ligne + matchée par matches() + """ + if line is None: + return self.__is_end_inc + else: + mo = self.re_end_inc.match(line) + if mo is not None: + if mo.group(2) == self.file: + return 1 + return 0 + + def end_inc(self): + """retourner une ligne d'inclusion dépliée de type 'end' construite + avec self.before et self.file + """ + return self.end_inc_templ % (self.before, self.c, self.file) + + def is_require(self, line=None): + """Si line==None, retourner vrai si la ligne matchée par matches() est + une ligne d'inclusion de type 'require'. + + si line!=None, retourner vrai si la ligne est une ligne d'inclusion de + type 'require' dont le nom de fichier correspond à la ligne matchée par + matches() + """ + if line is None: + return self.__is_require + else: + mo = self.re_require.match(line) + if mo is not None: + if mo.group(2) == self.file: + return 1 + return 0 + + def require(self): + """retourner une ligne d'inclusion de type 'require' construite avec + self.before et self.file + """ + return self.require_templ % (self.before, self.c, self.file) + + def is_provide(self, line=None): + """Si line==None, retourner vrai si la ligne matchée par matches() est + une ligne d'inclusion de type 'provide'. + + si line!=None, retourner vrai si la ligne est une ligne d'inclusion de + type 'provide' dont le nom de fichier correspond à la ligne matchée par + matches() + """ + if line is None: + return self.__is_provide + else: + mo = self.re_provide.match(line) + if mo is not None: + if mo.group(2) == self.file: + return 1 + return 0 + + def provide(self): + """retourner une ligne d'inclusion de type 'provide' construite avec + self.before et self.file + """ + return self.provide_templ % (self.before, self.c, self.file) + +################################################## +# replier un fichier + +def fold(file, level=0, il=None, recursive_update=0, parent_print_processing_maybe=None, **ignored): + """Replier un fichier: c'est l'opération inverse de unfold_or_update + """ + if il is None: il = InterestingLine() + + pf = inc_abspath(file) + + # vérifier la présence du fichier + if not path.exists(pf): + _print(level, "not found: %s" % pf) + return + + # Il ne faut pas changer le répertoire courant en sortant de cette + # fonction. Faire une copie d'abord + cwd = os.getcwd() + + # Se placer dans le répertoire du fichier + p, f = path.split(pf) + os.chdir(p) + + try: + tf = TextFile(pf, lines=BLines()) + try: + old = tf.readlines() + except: + # impossible de lire le fichier + _print(level, "unable to read: %s" % pf) + return + new = [] + + printed = [0] + def print_processing_maybe(level=level, file=file, printed=printed, parent_print_processing_maybe=parent_print_processing_maybe): + if parent_print_processing_maybe is not None: + parent_print_processing_maybe() + if not printed[0]: + _print(level, "processing: %s" % file) + printed[0] = 1 + + folding = 0 # est-on en train de replier? + was_modified = 0 + for line in old: + if not folding: + if il.matches(line): + if il.is_require(): + if recursive_update: + fold(il.file, level=level + 1, + il=il.copy(), recursive_update=recursive_update, + parent_print_processing_maybe=print_processing_maybe) + new.append(il.require()) + if il.require() != line: was_modified = 1 + elif il.is_provide(): + if recursive_update: + fold(il.file, level=level + 1, + il=il.copy(), recursive_update=recursive_update, + parent_print_processing_maybe=print_processing_maybe) + new.append(il.provide()) + if il.provide() != line: was_modified = 1 + elif il.is_inc(): + if recursive_update: + fold(il.file, level=level + 1, + il=il.copy(), recursive_update=recursive_update, + parent_print_processing_maybe=print_processing_maybe) + new.append(il.inc()) + if il.inc() != line: was_modified = 1 + elif il.is_start_inc(): + if il.start_inc() != line: was_modified = 1 + folding = 1 + + if was_modified: print_processing_maybe() + else: + new.append(line) + else: + if il.is_end_inc(line): + if recursive_update: + fold(il.file, level=level + 1, + il=il.copy(), recursive_update=recursive_update, + parent_print_processing_maybe=print_processing_maybe) + print_processing_maybe() + _print(level, "f %s" % il.file, verbose) + new.append(il.inc()) + was_modified = 1 + folding = 0 + + if folding: + # on a trouvé une inclusion non terminée. ne pas modifier le fichier + _print(level, "warning: %s include not properly ended, ignored" % il.file) + was_modified = 0 + + if was_modified: + tf.writelines(new) + finally: + os.chdir(cwd) + + +################################################## +# déplier un fichier + +MODIFIED = 2 +UNMODIFIED = 1 +ERROR = 0 + +def unfold_or_update(file, level=0, il=None, recursive_update=0, files=None, provided=None, parent_print_processing_maybe=None, **ignored): + """déplier un fichier. Seul le fichier file est modifié le cas échéant si + files!=None + + retourner MODIFIED si le fichier a été lu depuis le disque ou s'il a été + modifié, UNMODIFIED s'il faut utiliser le cache de lecture files, ERROR si + erreur (aucune modification effectuée) + """ + if il is None: il = InterestingLine() + + pf = inc_abspath(file) + + # Si le fichier a déja été traité, nous pouvons retourner + if files is not None and files.has_key(pf): + return UNMODIFIED + + # liste des fichiers déjà traités. + can_write = recursive_update # doit-on mettre à jour le fichier sur disque? + if files is None: + can_write = 1 + files = {} + # liste des fichiers déjà inclus + if provided is None: + provided = {} + + printed = [0] + def print_processing_maybe(level=level, file=file, printed=printed, parent_print_processing_maybe=parent_print_processing_maybe): + if parent_print_processing_maybe is not None: + parent_print_processing_maybe() + if not printed[0]: + _print(level, "processing: %s" % file) + printed[0] = 1 + + # vérifier la présence du fichier + if not path.exists(pf): + if level == 0: + _print(level, "not found: %s" % pf) + else: + print_processing_maybe() + _print(level - 1, "X %s (not found)" % file) + return ERROR + + # Il ne faut pas changer le répertoire courant en sortant de cette + # fonction. Faire une copie d'abord + cwd = os.getcwd() + + # Se placer dans le répertoire du fichier + p, f = path.split(pf) + os.chdir(p) + + try: + tf = TextFile(pf, lines=BLines()) + try: + old = tf.readlines() + except: + # impossible de lire le fichier + if level == 0: + _print(level, "unable to read: %s" % pf) + else: + _print(level - 1, "X %s (unable to read)" % file) + return ERROR + new = [] + files[pf] = tf + provided[pf] = 1 + + unfolding = None # est-on en train de déplier? + was_modified = 0 + for line in old: + if unfolding is None: + if il.matches(line): + if il.is_require(): + # traiter le cas @require + _debug("require\n file=%s\n provided=\n %s" % (il.file, string.join(provided.keys(), '\n '))) + if not il.top_level and not provided.has_key(il.pf): + print_processing_maybe() + _print(level, "R %s (is required)" % il.file) + new.append(il.require()) + if il.require() != line: was_modified = 1 + elif il.is_provide(): + # traiter le cas @provide + _debug("provide\n file=%s\n provided=\n %s" % (il.file, string.join(provided.keys(), '\n '))) + provided[il.pf] = 1 + new.append(il.provide()) + if il.provide() != line: was_modified = 1 + elif il.is_inc(): + # traiter le cas @include + if unfold_or_update(il.file, level=level + 1, + il=il.copy(), recursive_update=recursive_update, + files=files, provided=provided, + parent_print_processing_maybe=print_processing_maybe): + print_processing_maybe() + _print(level, "U %s" % il.file, verbose) + new.append(il.start_inc()) + new.extend(files[il.pf].lines) + new.append(il.end_inc()) + was_modified = 1 + else: + new.append(il.inc()) + if il.inc() != line: was_modified = 1 + elif il.is_start_inc(): + unfolding = [] + if il.start_inc() != line: was_modified = 1 + else: + new.append(line) + else: + if il.is_end_inc(line): + status = unfold_or_update(il.file, level=level + 1, + il=il.copy(), recursive_update=recursive_update, + files=files, provided=provided, + parent_print_processing_maybe=print_processing_maybe) + if status == MODIFIED: + if unfolding != files[il.pf].lines: + print_processing_maybe() + _print(level, "u %s" % il.file, verbose) + was_modified = 1 + new.append(il.start_inc()) + new.extend(files[il.pf].lines) + new.append(il.end_inc()) + if il.end_inc() != line: was_modified = 1 + elif status == UNMODIFIED: + new.append(il.start_inc()) + new.extend(files[il.pf].lines) + new.append(il.end_inc()) + if il.end_inc() != line: was_modified = 1 + elif status == ERROR: + print_processing_maybe() + _print(level, "X %s (not found)" % il.file) + new.append(il.start_inc()) + new.extend(unfolding) + new.append(il.end_inc()) + if il.end_inc() != line: was_modified = 1 + unfolding = None + else: + unfolding.append(line) + + if unfolding is not None: + # on a trouvé une inclusion non terminée. ne pas modifier le fichier + print_processing_maybe() + _print(level, "warning: %s include not properly ended, ignored" % il.file) + was_modified = 0 + + if was_modified: + if new == tf.lines: + return UNMODIFIED + print_processing_maybe() + tf.lines[:] = new + if can_write: + tf.writelines() + + return MODIFIED + finally: + os.chdir(cwd) + +################################################## +# lecture des paramètres + +def update_inc_params(args=None, basedir=None): + if args is None: args = sys.argv[1:] + + global verbosity, debug, incpaths + pr = {'action': unfold_or_update, + 'exclude_names': ['CVS/', '.svn/'], + 'exclude_paths': [], + 'il': None, + 'recursive_update': 0, + } + + args_read = False + while not args_read: + config_file = None + + opts, args = get_args(args, 'qvDuRfx:X:I:*@C:', + ['quiet', 'verbose', 'debug', + 'update', 'unfold', 'recursive-update', + 'fold', + 'exclude-names=', 'exclude-paths', 'include-paths', + 'config-file=', 'basedir=', + ]) + + for opt, value in opts: + if opt in ('-q', '--quiet'): + verbosity = quiet + elif opt in ('-v', '--verbose'): + verbosity = verbose + elif opt in ('-D', '--debug'): + debug = 1 + elif opt in ('-u', '--update', '--unfold'): + pr['action'] = unfold_or_update + elif opt in ('-R', '--recursive-update'): + pr['recursive_update'] = 1 + elif opt in ('-f', '--fold'): + pr['action'] = fold + elif opt in ('-x', '--exclude-names'): + if ':' in value: + pr['exclude_names'] = filter(None, string.split(value, ':')) + else: + pr['exclude_names'].append(value) + elif opt in ('-X', '--exclude-paths'): + if ':' in value: + pr['exclude_paths'] = filter(None, string.split(value, ':')) + else: + pr['exclude_paths'].append(value) + elif opt in ('-I', '--include-paths'): + if incpaths is None: init_incpaths() + set_incpaths(value) + elif opt in ('-*',): + pr['il'] = InterestingLine('*') + elif opt in ('-@',): + pr['il'] = InterestingLine('@') + elif opt in ('-C', '--config-file'): + config_file = value + elif opt in ('--basedir',): + basedir = path.abspath(value) + + if config_file is not None: + cf = ShConfigFile(config_file, raise_exception=False) + if cf.is_valid(): + args = split_args(cf.get('update_inc_options', '')) + split_args(cf.get('update_inc_args', '')) + args + else: + ewarn("Impossible de lire le fichier de configuration: %s" % config_file) + + args_read = config_file is None + + # transformer tous les chemins de args en chemins absolus. + if basedir is None: basedir = os.getcwd() + for i in range(len(args)): + args[i] = path.abspath(path.join(basedir, args[i])) + + return pr, args + +def update_inc(pr, args=None, action=None): + global incpaths + if incpaths is None: init_incpaths() + + if action is not None: + action = {'fold': fold, + 'unfold': unfold_or_update, + 'update': unfold_or_update, + 'unfold_or_update': unfold_or_update, + }.get(action, None) + if action is None: action = pr['action'] + + if args is None: args = [] + for arg in args: + if path.isdir(arg): + for file in select_files(arg, pr['exclude_names'], pr['exclude_paths']): + action(file, il=pr['il'], recursive_update=pr['recursive_update']) + else: + action(arg, il=pr['il'], recursive_update=pr['recursive_update']) + +if __name__ == '__main__': + pr, args = update_inc_params() + update_inc(pr, args) diff --git a/pyulib/src/uapps/tasks/__init__.py b/pyulib/src/uapps/tasks/__init__.py new file mode 100644 index 0000000..4c31b97 --- /dev/null +++ b/pyulib/src/uapps/tasks/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = ('get_tasks_itf',) + +from itfctl import get_tasks_itf diff --git a/pyulib/src/uapps/tasks/httpd/__init__.py b/pyulib/src/uapps/tasks/httpd/__init__.py new file mode 100644 index 0000000..590f57e --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = () diff --git a/pyulib/src/uapps/tasks/httpd/server.py b/pyulib/src/uapps/tasks/httpd/server.py new file mode 100644 index 0000000..3cc5f6d --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/server.py @@ -0,0 +1,572 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = ('Server',) + +import re, cgi, traceback + +from ulib.base.base import make_prop, make_delegate +from ulib.base.uio import _s +from ulib.base.output import enote +from ulib.base.args import split_args, join_args, get_args +from ulib.base.dates import Date, parse_date, FR_DATEF +from ulib import json +from ulib.web import web, Application, Page, defaults, nocache +from ulib.tasks import * + +from ulib.base.words import plural +TEMPLATE_GLOBALS = {'plural': plural, + } + +class TaskView(object): + _t = None + def __init__(self, t): self._t = t + def __getattr__(self, name): return getattr(self._t, name) + + # pour l'affichage + def _htmlize(self, s): + s = cgi.escape(s) + s = s.replace(u'\n', u'
\n') + return s + + def get_htbody(self, truncate=None): + body = self._t.get_body() or u'' + if truncate is not None and len(body) > truncate: + body = u'%s...' % body[:truncate] + return self._htmlize(body) + htbody = property(get_htbody) + + TASK_TYPES = {TASK_TYPE: u'task-type', + PLAN_TYPE: u'task-type', + EVENT_TYPE: u'event-type', + IDEA_TYPE: u'idea-type', + ACTIVITY_TYPE: u'activity-type', + } + + def get_css_classes(self): + css_classes = [] + t = self._t + # type + type = t.get_type() + css_classes.append(self.TASK_TYPES.get(type, u'%s-type' % type)) + # urgence + if t.is_urgent(): css_classes.append(u'urgent') + if t.is_normal(): css_classes.append(u'normal') + if t.is_planned(): css_classes.append(u'planned') + if t.is_obsolete(): css_classes.append(u'obsolete') + if t.is_done(): css_classes.append(u'done') + return u' '.join(css_classes) + css_classes = property(get_css_classes) + + # pour l'édition: champs, valeurs, et classes des champs + ep_items = property(lambda self: self._t.woutput()) + def ep_class(self, name): + return {'title': 'l', 'body': 'textarea', + 'contexts': 'l', 'projects': 'l'}.get(name, 's') + def ep_style(self, name): + if self.ep_class(name) == 'textarea': + nblines = (self._t.body or u'').count("\n") + 1 + if nblines >= 7: return u"height: %iem;" % nblines + return u"" + +def ensure_task(t): + if isinstance(t, TaskView): return t._t + elif isinstance(t, Task): return t + else: return None + +def ensure_taskview(t): + if isinstance(t, Task): return TaskView(t) + elif isinstance(t, TaskView): return t + else: return None + +class Server(Application): + PORT = 1974 + + def __init__(self, basedir=None, templatedir=None, host=None, port=None, debug=None, + tsrc=None, ctl=None): + self.template_globals = TEMPLATE_GLOBALS + + Application.__init__(self, basedir, templatedir, host, port, debug) + + if ctl is None: ctl = TasksCtl(tsrc) + self._ctl = ctl + self.OPTIONS = ctl.OPTIONS + self.FILTER_OPTIONS + self.LONG_OPTIONS = ctl.LONG_OPTIONS + self.FILTER_LONG_OPTIONS + self._ioptions = [] + + self.reset_hts() + self.reset_ts() + + _ctl, ctl = make_prop('_ctl', setter=False)[:2] # instance de TasksCtl + + FILTER_OPTIONS = 'aqdln' + FILTER_LONG_OPTIONS = ['show-all', 'show-showable', 'show-doable', 'show-body', 'no-stats'] + _ioptions = None # options initiales + _ifilter = None # filtre initial sous forme de chaine + + def is_option(self, option, value): + if option in ('-a', '--show-all'): pass + elif option in ('-q', '--show-showable'): pass + elif option in ('-d', '--show-doable'): pass + elif option in ('-l', '--show-body'): pass + elif option in ('-n', '--no-stats'): pass + elif self.ctl.is_option(option, value): return True + else: return False + if option.startswith('--'): + self._ioptions.append('%s=%s' % (option, value)) + else: + self._ioptions.append('%s%s' % (option, value)) + return True + + RE_PIPE = re.compile(ur'\s*\|\s*') + def process_args(self, args): + ioptions = self._ioptions + if ioptions and args[0:1] and args[0].startswith('-'): + ioptions.append('--') + ioptions.extend(args) + self._cfilters = cfs = filter(None, self.RE_PIPE.split(join_args(ioptions))) + self._ifilter = cfs and cfs[0] or u'' + + def before_start(self): + enote(u"Lancement du serveur de tâches sur http://%s:%i" % (self.HOST, self.PORT)) + + SHOW_ALL, SHOW_SHOWABLE, SHOW_DOABLE = 0, 1, 2 + + def list_tasks(self, cfilter=None, ifilter=None): + if ifilter is None: ifilter = self._ifilter + if cfilter is None: cfilter = ifilter + + fargs = split_args(cfilter) + options, fargs = self.ctl.get_args(fargs, self.FILTER_OPTIONS, self.FILTER_LONG_OPTIONS) + showwhat = None + showbody = False + showstats = True + for option, value in options: + if option in ('-a', '--show-all'): showwhat = self.SHOW_ALL + elif option in ('-q', '--show-showable'): showwhat = self.SHOW_SHOWABLE + elif option in ('-d', '--show-doable'): showwhat = self.SHOW_DOABLE + elif option in ('-l', '--show-body'): showbody = True + elif option in ('-n', '--no-stats'): showstats = False + elif self.ctl.is_option(option, value): pass + + store = self.ctl.get_stores() + ids, text, props, projects, contexts, filtered = self.ctl.get_filters(fargs, store=store) + if showwhat is None: + if filtered: showwhat = self.SHOW_ALL + else: showwhat = self.SHOW_SHOWABLE + + ts = [TaskView(t) for t in store.find_tasks(ids, text, props, projects, contexts)] + nb_total = len(ts) + if showwhat == self.SHOW_ALL: + pass + elif showwhat == self.SHOW_SHOWABLE: + ts = [t for t in ts if t.is_showable()] + elif showwhat == self.SHOW_DOABLE: + ts = [t for t in ts if t.is_doable()] + ts.sort(Task.cmp, reverse=True) + nb_showable = len(ts) + + tasks = [t for t in ts if t.is_task_type()] + nb_tasks = len(tasks) + nb_done = len([t for t in tasks if t.is_done()]) + + stats = web.storage(showwhat=showwhat, + showbody=showbody, showstats=showstats, + nb_showable=nb_showable, nb_total=nb_total, + nb_tasks=nb_tasks, nb_done=nb_done) + return cfilter, stats, ts + + def ht(self, t): + return self.render.task(t) + + # cache du rendu des tâches + _hts, hts = make_prop('_hts', setter=False)[:2] + + def reset_hts(self): + self._hts = {} + + def update_hts(self, *ts, **kw): + force = kw.get('force', False) + hts = self._hts + for t in [ensure_taskview(t) for t in ts]: + tid = t.id + if force or not hts.has_key(tid): + hts[tid] = self.ht(t) + + def remove_from_hts(self, *ts): + if self._hts is None: return + hts = self._hts + for t in ts: + tid = t.id + if hts.has_key(tid): + del hts[tid] + + # requêtes précédentes + _cfilters, cfilters = make_prop('_cfilters')[:2] + + def new_cfilter(self, cfilter): + if cfilter is not None and cfilter not in self._cfilters: + self._cfilters.append(cfilter) + + # cache du résultat de la recherche + _cfilter, cfilter = make_prop('_cfilter')[:2] + _stats, stats = make_prop('_stats')[:2] + _ts, ts = make_prop('_ts')[:2] + + def reset_ts(self, reset_cfilter=False): + if reset_cfilter: self._cfilter = None + self._stats = None + self._ts = None + + def refresh(self): + self.reset_hts() + self.reset_ts() + + def set_query(self, cfilter=None, ifilter=None): + # vérifier s'il faut utiliser le cache de requête + if cfilter is None: cfilter = self._cfilter + if cfilter != self._cfilter or ifilter is not None: + self._cfilter = cfilter + self.reset_ts() + self.new_cfilter(cfilter) + return cfilter + + def set_default_query(self): + self.reset_ts(True) + + def reload_maybe(self): + stores = self.ctl.get_stores() + if stores.should_reload(): + stores.load() + self.refresh() + + def get_hts(self, cfilter=None, ifilter=None): + cfilter = self.set_query(cfilter, ifilter) + if self._ts is None: + # reconstruire le cache de requête + self._ts = [] # pour être sûr d'avoir une liste en cas d'exception + self._cfilter, self._stats, self._ts = self.list_tasks(cfilter, ifilter) + self.update_hts(*self._ts) + return self._cfilter, self._stats, self._ts, self._hts + + def encoder(self, obj): + if isinstance(obj, Date): return obj.format(FR_DATEF) + raise TypeError(repr(obj) + " is not JSON serializable") + def dumps(self, obj): + return json.dumps(obj, default=self.encoder) + + def dt(self, t, all_values=True): + """Transformer une tâche en un dictionnaire dont les clés sont les + attributs de la tâche. + + Si all_values==True, inclure les attributs pour lesquels il n'y a pas + de valeur définie, avec la valeur None. + + Les attributs du dictionnaire sont: tid, type, cdate, ddate, dates, + mindate, maxdate, date, title, body, contexts, projects, sid, id, + title1, task_type, event_type, idea_type, due_date, delay, urgent, + normal, planned, obsolete, done, urgency, showable, doable, purgeable + """ + td = t.get_all_props(all_values=all_values) + for name in ('sid', 'id', 'title1', + 'task_type', 'event_type', 'idea_type', + 'due_date', 'delay', + 'urgent', 'normal', 'planned', 'obsolete', 'done', + 'urgency', + 'showable', 'doable', 'purgeable', + ): + value = getattr(t, name) + if all_values or value is not None: + td[name] = value + return td + + def jsencode_tasks(self, cfilter, stats, ts): + dts = [self.dt(t) for t in ts] + return self.dumps(cfilter), self.dumps(dts), self.dumps(stats) + + def add(self, args): + t = self.ctl.get_wstore().new_task_from_clargs(args) + t.save() + self.update_hts(t, force=True) + self.reset_ts() + return t + + def get_tasks(self, tids=None, query=None, required=True, unique=False): + stores = self.ctl.get_stores() + if tids: ts = filter(None, [stores.get_by_id(tid) for tid in tids]) + elif query: ts = self.list_tasks(query)[2] + else: ts = [] + if (required or unique) and not ts: + raise ValueError("No tasks found") + elif unique and len(ts) != 1: + raise ValueError("Too many tasks found") + return ts + + def edit(self, tids=None, query=None, **props): + t = self.get_tasks(tids, query, unique=True)[0] + t.winput(props) + t.save() + self.update_hts(t, force=True) + return t + + DO = 'do' + NODO = 'nodo' + + def do_or_nodo(self, action, tids=None, query=None): + stores = self.ctl.get_stores() + ts = self.get_tasks(tids, query) + try: + for t in ts: + if action == self.DO: t.do() + elif action == self.NODO: t.nodo() + else: raise ValueError("Invalid action: %s" % action) + except: + stores.revert() + self.refresh() + raise + stores.save() + self.update_hts(force=True, *ts) + return ts + + START = 'start' + STOP = 'stop' + TOGGLE = 'toggle' + + def start_stop_or_toggle(self, action, tids=None, query=None): + stores = self.ctl.get_stores() + ts = self.get_tasks(tids, query) + try: + for t in ts: + if action == self.START: t.start() + elif action == self.STOP: t.stop() + elif action == self.TOGGLE: + if t.is_started(): t.stop() + else: t.start() + else: raise ValueError("Invalid action: %s" % action) + except: + stores.revert() + self.refresh() + raise + stores.save() + self.update_hts(force=True, *ts) + return ts + + def plan(self, args=None, tids=None): + date = Date() + options, args = get_args(args or [], 'd:', ['date=']) + for option, value in options: + if option in ('-d', '--date'): + if value: date = parse_date(value) + else: date = None + query = self.cfilter = join_args(args) + + stores = self.ctl.get_stores() + ts = self.get_tasks(tids, query) + try: + for t in ts: t.plan(date) + except: + stores.revert() + self.refresh() + raise + stores.save() + self.update_hts(force=True, *ts) + return ts + + def remove(self, tids=None, query=None): + stores = self.ctl.get_stores() + ts = self.get_tasks(tids, query) + self.remove_from_hts(*ts) + try: + for t in ts: + t.get_store().remove(ensure_task(t)) + except: + stores.revert() + self.refresh() + raise + stores.save() + self.reset_ts() + return ts + +class page(Page): + __abstract_page__ = True + + reload_timeout = 5 + + ctl = make_delegate('app.ctl') + cfilter = make_delegate('app.cfilter') + stats = make_delegate('app.stats') + ts = make_delegate('app.ts') + hts = make_delegate('app.hts') + + cfilters = make_delegate('app.cfilters') + + @defaults(query=None) + def index(self): + try: + self.app.get_hts(self.query) + s = self.stats + msg = u"%i / %s " % (s.nb_tasks - s.nb_done, plural(u"%i tâche%#", s.nb_tasks)) + if s.nb_total != s.nb_tasks: + msg += plural(u", %i ligne%# au total", s.nb_total) + if s.nb_total != s.nb_showable: + msg += plural(u", %i ligne%# masquée%#", s.nb_total - s.nb_showable) + self.add_msg(msg, level=0) + except: + traceback.print_exc() + self.add_last_error_msg() + return self.r('index', self) + +class action(page): + PATH = r'/action' + + reload_timeout = None # pas de rechargement automatique + + MSG_FADE = (None, u'msg_fade') # classes css pour le message + + @defaults(query=None) + def doSearch(self): + self.app.set_query(self.query) + return self.redirect('/') + + def doAdd(self, args): + t = self.app.add(args) + self.add_msg(u"La tâche #%s a été créée avec succès" % t.id, self.MSG_FADE, level=1) + return self.redirect('/') + + @defaults(tids=[], query=None) + def doEditForm(self): + t = self.app.get_tasks(self.tids, self.query, unique=True)[0] + return self.redirect('/edit', tid=t.id) + + @defaults(tids=[], query=None, savebtn=None, cancelbtn=None) + def doEdit(self): + if self.cancelbtn is None: + input = web.input() + props = {} + for name in Task.all_properties(): + props[name] = input.get(name, None) + t = self.app.edit(self.tids, self.query, **props) + self.add_msg(u"La tâche #%s a été modifiée avec succès" % t.id, self.MSG_FADE, level=1) + return self.redirect('/') + + @defaults(tids=[], query=None) + def doDoOrNodo(self, action): + self.app.do_or_nodo(action, self.tids, self.query) + return self.redirect('/') + + @defaults(tids=[], query=None) + def doStartStopOrToggle(self, action): + self.app.start_stop_or_toggle(action, self.tids, self.query) + return self.redirect('/') + + @defaults(tids=[], query=None) + def doPlan(self, args=None): + if args is None: args = split_args(self.query) + self.app.plan(args, self.tids) + return self.redirect('/') + + @defaults(tids=[], query=None) + def doRemove(self): + ts = self.app.remove(self.tids, self.query) + msg_value = u"Suppression de %i tâche(s): %s" % (len(ts), + u", ".join(["#" + t.id for t in ts])) + self.add_msg(msg_value, self.MSG_FADE, level=1) + return self.redirect('/') + + def doRefresh(self): + self.app.refresh() + return self.redirect('/') + + def doSetDefaultQuery(self): + self.app.set_default_query() + return self.redirect('/') + + @defaults(store=None) + def doSetWStore(self): + self.app.ctl.get_stores().set_wstore(self.store) + return self.redirect('/') + + SEARCH = 'search' + ADD = 'add' + EDIT_FORM = 'edit' + EDIT = '__edit__' + DO = Server.DO + NODO = Server.NODO + START = Server.START + STOP = Server.STOP + TOGGLE = Server.TOGGLE + PLAN = 'plan' + REMOVE = 'remove' + REFRESH = 'refresh' + DEFAULTS = 'defaults' + SET_WSTORE = '__sws__' + + ACTIONS = {'S': SEARCH, 'L': SEARCH, 'l': SEARCH, 'ls': SEARCH, 'list': SEARCH, + 'A': ADD, 'a': ADD, 'N': ADD, 'n': ADD, 'new': ADD, + 'E': EDIT_FORM, 'e': EDIT_FORM, 'ed': EDIT_FORM, + 'D': DO, 'd': DO, + 'nd': NODO, + 's': START, + 'k': STOP, + 'T': TOGGLE, 't': TOGGLE, + 'P': PLAN, 'p': PLAN, 'pl': PLAN, + 'rm': REMOVE, 'del': REMOVE, 'delete': REMOVE, + 'R': REFRESH, + 'D': DEFAULTS, 'default': DEFAULTS, + } + + @nocache + @defaults(name=None, query=None, tids=[], searchbtn=None, dobtn=None, nodobtn=None) + def action(self): + # Calculer l'action à exécuter + args = split_args(self.query) + action = self.name + if action is None: + # tester s'il s'agit d'une commande + if args is not None and args[0:1] and args[0][:1] == u'/': + action = _s(args[0][1:]) + args = args[1:] + self.query = join_args(args) + else: + if self.searchbtn is not None: action = self.SEARCH + elif self.dobtn is not None: action = self.DO + elif self.nodobtn is not None: action = self.NODO + else: action = self.SEARCH + action = self.ACTIONS.get(action, action) + # toujours vérifier si les fichiers ont été modifiés sur disque + self.app.reload_maybe() + try: + self.clear_msgs(level=1) + if action == self.SEARCH: return self.doSearch() + elif action == self.ADD: return self.doAdd(args) + elif action == self.EDIT_FORM: return self.doEditForm() + elif action == self.EDIT: return self.doEdit() + elif action in (self.DO, self.NODO): + return self.doDoOrNodo(action) + elif action in (self.START, self.STOP, self.TOGGLE): + return self.doStartStopOrToggle(action) + elif action == self.PLAN: return self.doPlan(args) + elif action == self.REMOVE: return self.doRemove() + elif action == self.REFRESH: return self.doRefresh() + elif action == self.DEFAULTS: return self.doSetDefaultQuery() + elif action == self.SET_WSTORE: return self.doSetWStore() + else: raise ValueError("Invalid action: %s" % action) + except: + traceback.print_exc() + self.add_last_error_msg() + return self.index() + +class index(page): + PREFIX = r'/' + + @nocache + def index(self): + self.app.reload_maybe() + self.clear_msgs(0) + return page.index(self) + + @nocache + @defaults(tid=None) + def edit(self): + self.app.reload_maybe() + t = ensure_taskview(self.ctl.get_stores().get_by_id(self.tid)) + return self.r('edit', t) diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/ie.css b/pyulib/src/uapps/tasks/httpd/static/blueprint/ie.css new file mode 100644 index 0000000..f336f0e --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/ie.css @@ -0,0 +1,35 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 0.9 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* ie.css */ +body {text-align:center;} +.container {text-align:left;} +* html .column, * html div.span-1, * html div.span-2, * html div.span-3, * html div.span-4, * html div.span-5, * html div.span-6, * html div.span-7, * html div.span-8, * html div.span-9, * html div.span-10, * html div.span-11, * html div.span-12, * html div.span-13, * html div.span-14, * html div.span-15, * html div.span-16, * html div.span-17, * html div.span-18, * html div.span-19, * html div.span-20, * html div.span-21, * html div.span-22, * html div.span-23, * html div.span-24 {display:inline;overflow-x:hidden;} +* html legend {margin:0px -8px 16px 0;padding:0;} +sup {vertical-align:text-top;} +sub {vertical-align:text-bottom;} +html>body p code {*white-space:normal;} +hr {margin:-8px auto 11px;} +img {-ms-interpolation-mode:bicubic;} +.clearfix, .container {display:inline-block;} +* html .clearfix, * html .container {height:1%;} +fieldset {padding-top:0;} +textarea {overflow:auto;} +input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;} +input.text:focus, input.title:focus {border-color:#666;} +input.text, input.title, textarea, select {margin:0.5em 0;} +input.checkbox, input.radio {position:relative;top:.25em;} +form.inline div, form.inline p {vertical-align:middle;} +form.inline label {position:relative;top:-0.25em;} +form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;} +button, input.button {position:relative;top:0.25em;} \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/icons/cross.png b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/icons/cross.png new file mode 100755 index 0000000000000000000000000000000000000000..1514d51a3cf1b67e1c5b9ada36f1fd474e2d214a GIT binary patch literal 655 zcmV;A0&x9_P)uEoyT++I zn$b9r%cFfhHe2K68PkBu*@^<$y+7xQ$wJ~;c5aBx$R=xq*41Wo zhwQus_VOgm0hughj}MhOvs#{>Vg09Y8WxjWUJY5YW zJ?&8eG!59Cz=|E%Ns@013KLWOLV)CObIIj_5{>{#k%TEAMs_GbdDV`x-iYsGH z#=Z{USAQA>NY(}X7=3{K8#4^nI0$7`a(T+P4hBKZ7hk58-_j0w;$<(*=f7ic$nT z*Wgd55in08>183j3?S=MAoDDTLoLSL$!_UDxXqSf-?qdd@H%8(We~hQu&uVIo$6NV z(zMY7wn6r5i617ZGZ)-J($xXssTcN*&WujcIDRIp6J4_PqOvJ}9!p6+yo8LmAGS3~ xN#Qq?aIt$6X#&>gHs{AQG2a)rMyf zFQK~pm1x3+7!nu%-M`k}``c>^00{o_1pjWJUTfl8mg=3qGEl8H@}^@w`VUx0_$uy4 z2FhRqKX}xI*?Tv1DJd8z#F#0c%*~rM30HE1@2o5m~}ZyoWhqv>ql{V z1ZGE0lgcoK^lx+eqc*rAX1Ky;Xx3U%u#zG!m-;eD1Qsn@kf3|F9qz~|95=&g3(7!X zB}JAT>RU;a%vaNOGnJ%e1=K6eAh43c(QN8RQ6~GP%O}Jju$~Ld*%`mO1p and + + + Change Password + + + + Cancel + diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/screen.css b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/screen.css new file mode 100644 index 0000000..bb66b21 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/buttons/screen.css @@ -0,0 +1,97 @@ +/* -------------------------------------------------------------- + + buttons.css + * Gives you some great CSS-only buttons. + + Created by Kevin Hale [particletree.com] + * particletree.com/features/rediscovering-the-button-element + + See Readme.txt in this folder for instructions. + +-------------------------------------------------------------- */ + +a.button, button { + display:block; + float:left; + margin: 0.7em 0.5em 0.7em 0; + padding:5px 10px 5px 7px; /* Links */ + + border:1px solid #dedede; + border-top:1px solid #eee; + border-left:1px solid #eee; + + background-color:#f5f5f5; + font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif; + font-size:100%; + line-height:130%; + text-decoration:none; + font-weight:bold; + color:#565656; + cursor:pointer; +} +button { + width:auto; + overflow:visible; + padding:4px 10px 3px 7px; /* IE6 */ +} +button[type] { + padding:4px 10px 4px 7px; /* Firefox */ + line-height:17px; /* Safari */ +} +*:first-child+html button[type] { + padding:4px 10px 3px 7px; /* IE7 */ +} +button img, a.button img{ + margin:0 3px -3px 0 !important; + padding:0; + border:none; + width:16px; + height:16px; + float:none; +} + + +/* Button colors +-------------------------------------------------------------- */ + +/* Standard */ +button:hover, a.button:hover{ + background-color:#dff4ff; + border:1px solid #c2e1ef; + color:#336699; +} +a.button:active{ + background-color:#6299c5; + border:1px solid #6299c5; + color:#fff; +} + +/* Positive */ +body .positive { + color:#529214; +} +a.positive:hover, button.positive:hover { + background-color:#E6EFC2; + border:1px solid #C6D880; + color:#529214; +} +a.positive:active { + background-color:#529214; + border:1px solid #529214; + color:#fff; +} + +/* Negative */ +body .negative { + color:#d12f19; +} +a.negative:hover, button.negative:hover { + background-color:#fbe3e4; + border:1px solid #fbc2c4; + color:#d12f19; +} +a.negative:active { + background-color:#d12f19; + border:1px solid #d12f19; + color:#fff; +} diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/fancy-type/readme.txt b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/fancy-type/readme.txt new file mode 100644 index 0000000..85f2491 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/fancy-type/readme.txt @@ -0,0 +1,14 @@ +Fancy Type + +* Gives you classes to use if you'd like some + extra fancy typography. + +Credits and instructions are specified above each class +in the fancy-type.css file in this directory. + + +Usage +---------------------------------------------------------------- + +1) Add this plugin to lib/settings.yml. + See compress.rb for instructions. diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/fancy-type/screen.css b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/fancy-type/screen.css new file mode 100644 index 0000000..028e05b --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/fancy-type/screen.css @@ -0,0 +1,71 @@ +/* -------------------------------------------------------------- + + fancy-type.css + * Lots of pretty advanced classes for manipulating text. + + See the Readme file in this folder for additional instructions. + +-------------------------------------------------------------- */ + +/* Indentation instead of line shifts for sibling paragraphs. */ + p + p { text-indent:2em; margin-top:-1.5em; } + form p + p { text-indent: 0; } /* Don't want this in forms. */ + + +/* For great looking type, use this code instead of asdf: + asdf + Best used on prepositions and ampersands. */ + +.alt { + color: #666; + font-family: "Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua", Georgia, serif; + font-style: italic; + font-weight: normal; +} + + +/* For great looking quote marks in titles, replace "asdf" with: + asdf” + (That is, when the title starts with a quote mark). + (You may have to change this value depending on your font size). */ + +.dquo { margin-left: -.5em; } + + +/* Reduced size type with incremental leading + (http://www.markboulton.co.uk/journal/comments/incremental_leading/) + + This could be used for side notes. For smaller type, you don't necessarily want to + follow the 1.5x vertical rhythm -- the line-height is too much. + + Using this class, it reduces your font size and line-height so that for + every four lines of normal sized type, there is five lines of the sidenote. eg: + + New type size in em's: + 10px (wanted side note size) / 12px (existing base size) = 0.8333 (new type size in ems) + + New line-height value: + 12px x 1.5 = 18px (old line-height) + 18px x 4 = 72px + 72px / 5 = 14.4px (new line height) + 14.4px / 10px = 1.44 (new line height in em's) */ + +p.incr, .incr p { + font-size: 10px; + line-height: 1.44em; + margin-bottom: 1.5em; +} + + +/* Surround uppercase words and abbreviations with this class. + Based on work by Jørgen Arnor Gårdsø Lom [http://twistedintellect.com/] */ + +.caps { + font-variant: small-caps; + letter-spacing: 1px; + text-transform: lowercase; + font-size:1.2em; + line-height:1%; + font-weight:bold; + padding:0 2px; +} diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/doc.png b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/doc.png new file mode 100644 index 0000000000000000000000000000000000000000..834cdfaf48a509ca51d93250fb28dd12e5ea0a13 GIT binary patch literal 777 zcmV+k1NQuhP)XPw^Q4IIXsG~v#u_4t;x_HM16EQ@QRY+rut&97&UefsPmLrQ5P zBC2kcbux9L%2bJz$P$XV$*zSxb2e@6_3O#;&!FD<&hLjGn%~%en;7)djE^d6!t$lW7GyIOKlQ46hr`Z zjLNuRDP_53dNoN?wd&HMgL^m1DXFU<5dQsrceN>fSz00000)O9XRTNAz`{eoOom?Tf*9)f$7n8&|1&5M4#i^32;+&E? zC3Q;bRFQN#y*%%=_V)Mfa<$xe^kB0TO;vJPkN*k(2v-CI7)OaWj?&eKPos(H4wGh_ zIC;6#q1B5SMap5{(Hc0~XO7OfqZ=x{kupu8-H&9azl`L1pTuu^Znm3EA)kCoG=JuwsyNLEtY83i->Z~j3y~F)`RA1k>zTES07po!kBVS2y#L{jCt|CMY&v{ zxmqM|`OA#P2{R&)OcQd}v0kt6_Dh#`Z$i5_;q|93je3Q^PcfR{TmBHRmr;rWahz~G z2x-&;d_O~HkmKXt5Cd#Bs?-+qj3zOiUdU24KowBIUPg(gPNmxqX)Fiia~V*$y;5L( zrGNmU;81MA$F2k%oeUXQ@}N%bXz=qOij$4IYk4W=jfhDxfCz{PGXe-#ge#VfYTyoj zh4JvDePrW{lf(Oux2xG;VZmlSvDU+Qf@i=O!B`MLglhttCUHDIKkc7SE*sqBsxVsZ1NU-2;A-D&3cXziC+}$BK1b5fq?(R0opaTpr$dd27cfZ~J zW9!zvw`yy<=JeZh`t3e%pL4pWrk?&qC@DyyA`u}$K|!HPOMUwEdbe{=btHuOd8 z`A|^Yqjol`D(|E5)A3jzN@S+tk7d&7{_JB$b|h|-!+R$1nV5TvOk6n`M+HmlM{_nl z3kJ2VJkGjKYKm#&!?vQD8~2PQhX~Xj6Dzfj{NCD&+MUMY;$rW0)cxf7c;D4tGp7$P zPj_pR`DS0PDvG~QQ2$MiRhN2R4*343j>~-}ZcQv-UzOQ3TAYL`+I?7`9qicd>PMhG zc`q)^Q^uW7SJt{a`77`|R%nw*XK3XrhFfAgo#=9RKE#QapN}_G5Z!3nXT^k2xOWSA zADw5+^_ByeH*7Z=Ytd`wwYAuJV(iB2qO(p`J)urXrstAwT(dghQCEg)Pyv|a# z!oQ2ZaybX?3r9O`KGE?I8AM#?0mAa#Y55Ge$F3|&in%A5xC^S2oEtMK)~X*>x>)ON zaOKxtv*oCSMKaqq=GSWN8nTXuOaz?9v${v?t$3qu2LvjnDR~dkuCQx;HeVuTZAcAS zrHWk*a{Acn%dyqhZDW!d5i?$!VQy$*U3dLLz-11{<)37eM*Mq`|uTZW{}hbDo^Nd z^XP_t#o!#$#^AlqFw3e#SHTMxYN1{1EQM_krQ2EG7I^%$aS}%~? ziB~d<3zybnmq&1RZ(y~YN5Teh#wh=X^_MkD{#p)4xmcy(>$r7d7o|SmuQ_)6XyLcS z+yq?kstrmBQShkAS1%NrF3H?qRt&#RUu!3Wdog-dgDSp&BFY( z@kh;-R#CpBi5{|*>2lpP0M&hu!{qawkZtK;j$qNug}_k!;U7#kCxZ)TnoD$`21iLZ zCj^@j8$-;Y||(i^Ob~y zd0Tr6jnmsWLo+zlMX)i=lJbu%FooR-5KY!`u@DnW{rom*d;fvj*vHIc|Kg164 z3C*OWh4bTIi{5%m1}(S>fzJ1Q@w`8AW{Fy^`rAXSQ@aR293(8H& zYGik;yzWJcrq;5p9!xlB*8+@bdCd2s0Qf$p2bG%5F@L7q`96*iyf4F3BYAPizZM`D zjeJF<@&4-8#0;$vl6jg&$`IUsY}>gTAn8OgHl4&Ys6U#tf)+Rw;Wti?HIHn^JGoW2 z%cT9%V9c{lNtZ-2ckuTj{%p^zEa{6oWk3*#O}(gjWdpm1!0f8Lo&_y`9{11=6K=<| z(q^32F*qtmaf*6&ps^fL9Wa{%VAW>-VF+1G=Mc zo~-?1)LU`{$PB|}Xf1Q!(cs7J*;+z?eax${dpvSMqL3Y9X?;g~l(0auOk+8Nhvcxq z@3o2psZ0*u%PVZdbtO9l%iIh76rZI^vNrhgj`B~)!cxKu_t{CxUCXFR5L=*kKWF3i zPv^#M7h(u!N8dllDK(Q`HHvi#So36NLetL-|sn8G5+A}HYPDg2%p=Tob@VshGSXXgX9cUT|JF#_c_zmlLf%` z+sa-D0%zu{5D}vdCua}_I|cvDe_Droa1;cuFM3axwF~a^d2ktc1{pBXIK?v=2t04BNvW~i>WdIbx@&Q!Ue-GQ%{bW7qz`gJIB>&InG7kXkdHwzRZ zYY}hzb_25@Aj`v3W@6W$wC8CB@m%{#!Ni82hw*JiiaRXglN6t?ackf>&lWNCRM37V z1!=VUq}kV{ebp0O!?E_}imbJ21=dNn41xaN!}$Fx8wDySN~5aPQ-1*k9tmu+@*L@|?D`hu8XBj=4?E1|4$Wky%ECiD?VeZ~c+1Gm8JTIYf zb*5{-`dS_e!sr~vbd6SsVEieO`=JviaIxtCzC?xnbb`BI5f-H@o03N`+VN-p0W@!9 zj|EjpQ{SUA-bd3VU!PqYyIRi0J3Skw_?-TGo5+}H9mWQP5$nf7VkFb5M;diG$}i1E zqJef{OShz-%M3~UGNn#bMJv)!VRRl#G5eizR9J*SUxvs)>ZxRrnAb+m-v!Xy0r~P> zMFaH(*JLjfJDZR%hc{BtX%ZPp zm`bY51;X(xt3v(#zeyuq-QkqE7%ZerD?da-Se!=^^U+al7t-~r@nPS5&|YPckRXj^ z0Boi)NwPuCIsOF0$fzK*hQmeMDxAGgow{#0QnF*e;}6|EUHf;>{C-mtUYX)+O+q#b zqXz>lp_s*!vaSuCMHN922Uf453FD+lq`3E-^t=_lU*eUJE}lgdPlly;%4p!lRa8eD zES-%l(Q>%L(P7Sn$Tf_ywKg<~EPp(EE}gsC+jra`Z3LRK76opEG=8W5M3_AT3+qpH zl3jeU%XY#h(mpZgmciu?Mr^$JEf$6XXS+?oFjbfCc34MJfnjhJRR>cnbCcV!Ab9x4 zwBd`W6UNdp@4_%Txd`iSwj-0E;;stM_nSvK1gsW^XC!L|GL2b5PsH9lU|ke>A0Svr zD5!Xxtj>6DT5ioOLht#Jq4kpUg8kB;wBq3N0Q2+7*a-r(;%NKtl?w~&o-ZxXk}T!# zmveS@N#Dqpu@^tM|21w0HS#c{9i7{$Rs^O_PPj;KAQ?_hDjTLgRl{PUoDDNl9QZ_$ zso)h&AO-!s?vl~OfOoV_&e{HR8=GH(^iF2y3`0=b9RA&K_94%a0?=A3MJg(s9~rEyHELQ$cJg((m1VMW(gSawxkK^v8(O5$@B+uSJ@ zWfBHDMT#XvYwX^6&YI1Nlfeo%VabHK%CB4fj_NuKm@RO0GfV2k1rB$vw`J98{-TAK zaOlT8&LzJefJ6%pc0`?5TRB^(Iy^XF=Y34cjvTRKAlWc7Fq-c80e>({<|aaRPEXr_ zn6z4ys6|+DABlxpidcbX_n(2N&{SEy2NHbl&moKb^nfmQskG&hT33^O07KLENxkk| zDW+s-$2i}$$5g+zCmTmGe7q0^5TDx>!BtmtRfX!bbb7kvC`}J1mDE5jqJWqD ze5_9hEs5lYUa9HF?o^HR_B^ZOe}4}!)*(WDB~UZAUCT`yQci+$ANFWoU}rCP?BmvM zIYK{SHRFyvvLP%wYz%yxCm3kD=8h2^YN}&zo+BvAbw!|r%aFU)K!$ljn(X}0I=g6) zMkJ7c;3&s+ovD8I$4@@0%!*HbkuVB_Q@-Pna}ML9a6#_r$cciTX|{Da=U6cYvEGXt z{Xk(nzR=ACjBow914xzP1OlCUGxbZBCFs!XQ^Xst37($%rd9dkXfb@24%m&pPo?@p zdhTOTePd0G%4=^#3n=Wuef-wCsxcvT$*k+I+mfKG1yvKZne|x`s|1!wh3?Ej$5i`W zm?(B^?a`y77U_?I>4n^2i<6ZAEp9FRPRc)cLvsZWYrZck`?RClhx0uG%Ua*BJbKpK z+BPp`K$$8(Pr}(UoT#@$d$?~$q*+3-VZ|wv%7$2gZ(ATnXPuCz8b5QA@r-&Fs28@ z7Wrd&SNWBtKtY<9rQ;E}=O#mR|E$4_cHE{}0>Xd-t0RwR^uN)hk4k(uxJ)>0TwB*B zJ^e(3vHpytOo?gLn$&CprA76$7}Mv_eB#}Q}1+vG>o#sRHVXFMGly!n$d2&mzL_znIFz4d57=k^!g^xISho=+dO z(<@%KgG^5>CY>f3R=KGGMZEtagFpd;uCw*rq5+={uZxt;Uz!D^&5R$DxWN0zzn7x2 z(aZ@(H(S>0NkpvFdatC^tX!{Qch3G7f@MsxaYCO7^5uVYl)SQ2Pj)Dr=S>f;$@m|r z{TcdWVIN}g=S5ra<_#LF=i5sMbqGCSBm;AdO6&0FV`d+Td57Ogd6%jblx?VjA!DuIl#iLI~LLe^%Oz0mTgs zW4O5d8o=kz4Gj`WJtGtR6~+KmL%s#3*Y_qhVAl8=+=kO>VLMHfDc_P zAR@y9SJDASQsbZ1Ajt_UteEJCY~T)V_z%l4#f;E3ys%f=#@_9FP~kcJjyR`1)YDfHQPDYt_;#HUq)pn*_kr8mp~yYht@t`d3T8(u68Fe($%!si;b-YsSE!&h8CS*Qc?CI*$kW^_ zlvcIJJ=d!00WZ8#o5}w6(5>(n{H11E-F4HBLhk}}6wJvxgy0?@Mh&xiR|8eS`#`2MQG{_I-1>VCg_R^BqoKJC6`( zha0K{m`9dR3Wrwx%rSO+>0w8p%=)APH^u2oWm({SSo?!ry{Inefo-?sNx!Px4X&CVVKd;=5 zAM0N3tUJM_U3R4((NYSvC^mYrU>44L@S+eG`S5yR77!*?|POTyu^s&SUzGTm}O3US5zplvhc zdn&k|K7+d_^{FLA6%#70s<^4K?WbG*;wB*ov-R2G*|5$VWAU8>>UAur5z~nX<}{=I zNpSY}*UMPNCaHtA^+E!oQApV}i6Es(a94zq0YC0=S?D#$_0FeKlnP?6*r++tGyj(W z>r}8Z#t;A)qUaih80d*E(i*+>wSFSM zoCp3!4clT|b6 z%Z{|JjtN^2yv88FU+y!#$7q&e20J5nVf1G-I;z1B(w{CY-C#4xV~z;q>IdlJ?zD~l zqBLgr6OV=Wua&Mpq>^4x0Q*yf_fSL-rB|q7v%F&^sbtAz(#&hk^2#JY{EuwmsZka z7u$+JTzcegTg8tFM5}1u@rzZ0{g{Zz3#nngZ5be5tTGuSG8R?%%iiID@wDS-X&tf5Lvq$sj5AO8p?uqQ&>I6Oz5c8R<6O zSz$ikgtPQwaoTpG2&#`dcqCY`rtRUPd8Z{HMN4hm}ha#l6%mXg@#)2(%KbCVod}l zoK2~On!ix+?%7nPoG&(4|Ma>ma~N*f8U^%i2xPr3d*-S~c^gp~*@>%fw_hnb+&xiW zreuLJ!eVLzQ30VI05l8;=FIaqwx+<-&t})rj>~Gz+ z$PUP9a+Zz&XV2)8PJM}b?U7Y?pj}hZ-YzNPr7=5>rJp)VQs6ap^Skia-zKV(#L56z z+LW5sIWcx-zUD2Rw))*3mvK9iJE;m;`IQQS*jX0uK33$O^*Ge3gYux5E3{eGGmCSZfgbQtYrgF4&urMaH6ZLe6{f$nJP&t0g&UgnirW$^=_ z*=B5R)S!zY8e#)FF8X#t*rE$pP?%a*g=VYqZVx#!w@bs-7xf<{bywVhH=n)ku#fYM z7c)DnCXV@khqFbwJ_y{bB(g!TBH3eWx^ywL?lbAVYWhTJUMo&YA^1o}Nd%==%>Hm) zK)1>8H;*z`&LO$+Q{WqSY@EE`p8QhS_|ZtU(cvvDr1lUvAzgP-gtg2l_` z?4+GfjfWHQ2cegVc3_sYaD%;Y@-1wnUw1^VBBli2&+kS1jBeAvUHG~~&SKZ_HGv-G z1Y`yqYgcxzPBxS6!Dysx1hsx)l{~}7Tzn4O8}-E7u%KWleS*t;UKV?MgS*}I5?=m; zL(2zbU36_$zMtyRv3&R~F@}3^zj}{5JJOLS@24T$Et~t8Tt+pLDHq@!9nzhUzr4SJ zlD+F?UMelD!LW)~jY7Gh+{bYWE02MRoa^UcP1Yh~k2qY?FQJZ6^dzf&*l1UwN5In8 zY5W#W#xUR+J;M{iu_zcJDlgPC8valS!q-3k!eNVj+$EIn_jAqZD{!}Y>k1_bjlo+i zacb*|KyiJWxL`y{vxU*A}g}onO(q+gFyF4Y1Tcu5uXnao&}^VsFIl0cmB0&~~;zc$!5o8e}h| ziJSBDt^aPpp@K<{|F}K$C??NA?au^FbM~GS>|RcWo}uuo{r;gf>81iN=A; zHI#~3?*h=%Ve^4^Wy-^1d>5W^%=5gI3BbEr*vtLTVEvu@7qTrIE+4NCcK)MinUh#x z_Qw~;=aJm?sK*V)AN&!UvlDK^h5Nyde;=*Vmg;LMyX!;RbEmy?r}^~Rw=R9RbLlQd z2$d!XG3JFZIvu$W?fw`&5)nD24LYJ*&Y?=bFezuH=gKR#sl(HZv)dRPVGRT*F_4-W zrg#tY zr8VUQ{oJK!hc@bL44S3nJcY0?pxqJNmsy!!7yMhItOt<}w5wS4+zn#Ap=&Uh{jrTx`ov^Uynd1Z4eH-Oee&kFpk1Qfn?{e(#uktK{;5V@8;{u9#PfX< z4$E_s72xFBUq3!eDfNn&Zgd0J0us6?2+zS#qfnU{?X%gI5U!+a+xCLe>R8!pud`5y zhnb^e57|5g{!u_HHqT6y+#}l+_=?Loi@y{svoTG5W~6A3VZKm804NCtj}>gwLn^bc zyZygP^v1u2DDcTp2>& zB?0U%*3@~EHe*$-8(nNHQUD&(-b?RqHeUmVh9w45b9kPG> zJqp{RbdR7ar>23Ud|4*O}9p&iR(LH zO}{1c!YZl4C_(2C?&d3Ho&N~lOiZ2pFWM&u7eg3qo+Z|REH|NF=`KFo?=hB+ZekU1 zwX!G>Ph!VixLHo8#T1()I7Rd@i%|odQ9Pr3Cw*D}LHgiQ#wGkyHzUzsYUw%bgHXkL zeS7;R0Az;n35Jy&UXD0xORmVjdD;rIGT_CIsoK8!_OosjuIk|3;_QYBr|9$l#^5wx z=!~OSP5(-lC4@fHh-XULz-MRWuwZ{ATE{41hlE8XL0%uMnZ3qH3l1D&+uZxQCh8djwYmT{G#-ayF7k{uJ`iz7Tw`fDC{qlfMsn*qDXCeJa!xE z@Y12Hy5+4$IxcbOUU&L_ETlX3blB8bN|U1{0`nzJX!-BS@}Ze|;?FBFM_}=KWGs8P z3ri(hT_i_q1C&vNp)2KZ3LU7!d4U2V59Yn#9Q{2*8|4c(yh^Nj{1#6;Z^xP-#lX~Rx#pqv^x3)*pqlXn~Fzp>mynld{T5vWx3Qxq4S{O=72Lv1Z$0CQGAP-57a{ zxUtH}snlUVA|Gfbp|Y9e1qb-%wh{tqwA^tBwK3_MWkM?F#@c(}qpa1U^Q~rust7!Ct8LO^mhRBO-k4mpCTM378PSj;!fx zO-yA>B`jZ;w*w~XPI?{uR37;WD=Ybdc~-t_USx9?b%DN^o#~{3B>HiVAld48W_5yF z&j3nlS0&_B4kw@#qm~PnH=0(Q%GG&iFs!fK^rQR`nGEH*Z*){^B{Z1w=R4-}B;noD5-5XT{p9&F zH;4C|=`^JD12ZiU;o;pXGc@s+cJ&$upgETwDzw<-6e5_IBg(;woECF&WUlAGeU!Vt!FuPxAs6l~1aPma6wJGP51DWM$5b z<{$UJ{}@h*U6D!7u=0JbMZ&xNG z+{(_eJ6jw&gL7N|L7UiC-py}W8`%`dYn8@}H_ixCul;%)3ZrGy9f6w^9%-kEVYr^p z={KytKi}@mS*-H0N}mhC_ApNZVc1Qf>tUjTz-K~7%bQMOAZ!%#THrW0jO#8DYdmtu z=axO#mK-Z}}2tG(nwn9y4_cMBbnfx666tY#GpnsUTYbuHr_J5NqwM#33H?97;nQdNgAd z{P3yv0_60WD`7CEIEZF2&SFy^aOA#EX0enq>|FWHw~u8ADf!E&&(sfaaz*0gpAUng znuP!*a$#Sn!Cx-@O}7Fein|!20CtMBXDj8J{$Vv&bbXkshX_Bb^_J&|D^e-L!Ey1vP)FJrJk?vlEn&RaV$@k&v#y}=5$7#6vn8L8=*9_tdeWt zkR*s`Yv{=rpxfz^v-3?x_OpzF_(3Bs+C^zi*W{sF`JMj>CO^tKi&h%1=M(L+-|$mM zdT>Ng(+G#Gl|iPDfGir)QIg(uK+PK;PQL#EOh8EO^j7Hvb|$2VBNA3-YiPM;`oyjINI{qJ^m zN%PVI>Q0uv-PzUxcNIsIy#C4$o8*dRJJ-AAVjLY^`upQab@I_I3G1Cx>v`)|xA7?M zWyCvQ0jnn@rAbGJ&6d*C+@O*^Q=npEfvzI%(&tzJ5~9p4ZFBMLPMq@Rp^|eiD-upb zLl0jISwZ$BBz)gOH=EaZG8Oki%kETBZ3W~9;TTa};(&PqQ(a{5g3Ne}6j5U`lMp6jN8O_;Gjqi*7n%X!9Sv3LH&(vBK zzE5cYz9@v(3lDzomN|ZI@+2*96B1<__Sl2<+wT7ITc~L(oss@a4GI1S|c1uTcYjmS02=xE_tj<8EtjudV+3CZq) z5X$ADjt7SH;zDv+$*6;32D}KAX)C(RQePAVx#RQ3haD*G2L+bUZVnoHH*dMiH6K~` z@11(#J*#X2f0egPz2ur6IPW~~&}R2<_?~&$$;`s#id$SW5i1KC`iW$Q`DMmiesiVa z(52H9DXDBV5RrPxNoOoSzi{Jo%*^$~f#?n1X`9uFeD$pC?DgQToXZ}Q_qQlO06;FP z%P^5A0C|eL*kBj|hY>wOwY~;Js^=7!Y!;W9m-FP5C8sKzx2VYo+dAYzuKXy1d%Zqv zdmr+n<9q0+PlcVL^+3GqqeuL}$^-e*_+M}i9EZ?Tvf(c->GRB|>3EJyzNP>nd>e+x z1dVc{irhBb-12*yyOG0AcJO~Pv?*hU@43@cI@vh^`Q2dTjlzHQENyy5;UDqRK*p!_1bMH)|#yd9~oRPJ#OtE2j2Vn2@l^bZj7&M>M>Rbk= zWGyUG7{0hyuRO|{Tg1==BxHD~4^9#HPN^N~4Ne7kxW_hvn1RoSW?O0~U5F@p|Ll8* zWTCHE?3Z6+w?4%F18eUA^P&F-PqT14RobA4#~e{Y8w`uhI_Yf|>ZX$f8$ zbs(Brmy~z=9uy|O#}3^>fOPIIHa;Q0B+Hs7HotX~?KyN$6AHdRtz*~Gw-r8}C^gdC zv9)>BUGnZbIRRzn875o+ShIS}^rXAY8)&eMt+uZ0Rgglf z?Nu;-02s#1BYM!MM!e*AtaA>E`5n)aL*t#~^X) z>5APA-|K?S#3{QViyRx?Ecw2ym}GDI?6lN0q@g5_|w}4k;#idhxp5V)l%mAt^FUGWB5km)(iMK z{Dn{X8m)G6nVg6j#nSh}pHRQ&X5cn=xQUa2>&TCK(V22P#Y;Bz;YUAUCMq8 zt$u}$4;6_=kCNq#>VenZ((VCIj-eZOXh5wXpa-~KQ&Idcc+x<(dGU`6y?A6*Rk|ul4kdUT zXKz34yDAV>gK2Fu>_FWSOwM5$oxfzwAe?>71{jm*y_~^Cge|sEZ2n z%j4d^Y6jxQJq3}saXEz6AaneeT^)J#q8BpNrq%5pMp7+f-+a@J{Po0Pv!`4lCL5t! zsIfm-Phn}E!NyR^u_n}N=wW@(3!L4f9gwWZ5}f9 zN3V{Yeo2@4agoT~$7Z2vFF`0vq9hTCm?*L_dkV)POf<=wZW^IXcGfiTgjZ8A{CPHD zXg_}sk&%BxY$b#tV1O_XE3sA@j_ZyjP9jw(nR(K1Tu3?Q$m7NggGmw#q|<{`?n^pF z&zkXld;As#`TnD+&&CWnu1Ssjjbx#EHS9iZazhv|4A1Enrq3nDN68J+%nBE2fL*)L zX{RiRinE?HoLQz64x-H=Bq9CbS7#5_MuSc_wU(5|OX?~81&ePSgB{Kxg9x3yO}DHd zWcyu~U^b!c7s4kbiQW9|gS%9{MrP$h!OPUvj(@4mC7<^W`##j^T?VBkK8IL$xISIn z(4-^Bg>5#@QHy@{U*S=mw767GgWK?FBd*DonF8`re3`eZng|bNd&)g;kBDUvWBD7R z(?F@5&h9_}QtJXN?4DlKLz9G-d-`Vdem4#2Zq9K9$ZHayFR@43zNTirJk3 z>xSSJwRk@gU7^Gpftjt{@@%FjY@9AZx&;hlbOGeme$ft0%x-x+B*UKRha(ccwrby; z2bMJ%)+_AvK7vk_w__c;lBR>(ytrO!t_7;_?)oMA*CH#s-FEY~PVc1m=odhORYFES z?)@mM^^62y(M{BQQ9zU5qwWJKtG;S5HxA;<_lh_CT}3lJgW*=r5T~ALo;N<5!$ho0 z-uyPcY&E=>fV9djg~rmxZh2`Jxv?2imF!8{9OseKr;W>@9!~8zYPRXwC52JreN9i! z7vZW!C&=-vvcrzhUYYOQlY-BYh5ny2WOc#ifNjGHwEec-y*4M>UzFC%e@xCQ^sNY{ z7xbT#A{IE&y_Sfy#|S}7L*8O0AW1)BlBIg$|5CG+h<(J4N78BTsPYU_r}{yS$R_V7jS2culfdX7 zO6w5V!_k$E$tBc5w{d~9en)BOT9ek}lsx}X+U;E0Gq;cj$Ju49z5TjRSJe8as}qYQ zvu(Mj3^z!AGP%+QNdDF~^6MBTbpeS*xer{<{56xV$3l0nagXfa)x+LWs6%0tj?EIO z>v4Qv;5onPATM6StQX`cb@PENo$S{|zo#%iS$Auj-(r|a<`FPHt#FscQP6Vm7~vhI zo$79I{fr=1T9*WD4(eJq2W<7Uos-4YBKipaaqWTMK80c-nSwSGhS`#s%xXu)5V`!I zc(!ll8T@+uD+irUt6|eW4p;1pJ6Llz-x#4Ky+46eU>C~4a?1bo&DTliuk%QEZhb*q zAen1i?SobLu&2^RLk(5uhT>nYpsh3PHCmgh7jX83XNi+?7|pnh%$ul{vzTrXU|Gnk#ME2srMd{E#KI+@ut ze+kBBbM*ULNHK|q0i0zOT@gO5gF%0BIDX$P4dyqET@%6KnOrSWWG4L0jCvN&MVwr_ zP$J2^Ko?Yu8X*4_jp-joyXb0UC%}(com3fu&WIN82{izPE#=NB5MMq zOP_kiMNVN#o79B)d62WTgJD`O!8=@%!|=PIpm}B{IwNskFT^|OJU8gPyFl|!*oSUG zW#6Cd$7dG37?9q&en3)7Iq;Q}V~?3(j%;QE|K?O@kAPJ&GZ$PLiLTNCHj1#zc;K=Qu&c&cb6C=Y-jT&*JzDS z807fxM5A5xiH6y`&v^Bbpg*H;qs_v&>F>oTgdH$tqj^MZzX>z8Kw+N@kNlG|cE1Y+ z-$i|T3gc)$zhVF>9*^mDXcF7o>m@+rbAz~yF zDo`YKj3K*Y*Ap*cMF6WJ7SG1PSl_4@?%%Vm1vfS_bp`&04|Y68EphW|xh{s5bWCOhX*LrYbw}b|!J5`fTAmsaCBmtHCfqDf=bF52x!PD=&6P~S{u`|7L(FFSr_7&r!o)nQZ1wD04Q9Mqf#E)=ct_KqVTdCDL(}zrW6xaB@ zx^Xeerfr7eQt~ zO3BognAExhyu5f&^k(sAz2v;=*ypHPz3TyA?k6M*F5_%+Y!=ncs4mn$dp zPz%&Nrx{iqQv$QUsBTf!+-{eVTD(OOyL`C`VsIaYGGBy;<8RecKacZQTw=u)R6WPBT-)6@1 zz!3Ef<)nW1XTj6wn*VrGxhiREfJxU&GnU7t@**k2XEEVThSIp{pp*r+F2oNaD%W*j#u26Z*}ukz_uiPo>GYb z3+ocAKU?Z-?s90}50-uc?uR%i;`we27dvnOxr=k$P5Rqo040d-4zl^yz?N5G@;co{ z%75#h=*Dz+P45@n6Fx4`toFd#VL{M*@WKZ$8gkq-1~hW|Ecp?18lA->}c)A za#u&*3fb~N%p?9fOZ0w{+f(hvakHA@8*(*(<(E}RU#4S!>S>D6LU3CVgd1=(%S|v1 z21t0_eO(jTj*=tB76#>}w1JysEv>pl1iNp0{@aB7k6EcF&yV@PJtgoTkcXwkT634^ z(Lg~#tNhm@q!#giSxf$>i|>oj(ympoAx@|=yb~t869&E$#=k;cXAp8r3B_9^A|OaC zOA&G=Z#$)uNG<*U_>j*!kpQ|c6#=~CO&;K$c15s*-m(kHuuEpi@HCL%g%P~a<%Tz2 zFQ7mC6(yiM%=NO~B*m}6$h~S3BG?qL$ldcAb79bY=A+JgmUrl#;|Cd9?P=Gae;Ie_ zzBG7S26*|e2`>ZR{LI$9FP1OYn_r$Lz<{2Yo|-4JWpD*pq{B(DA2$;?b z@ZIAB{{if|6jwr-J%Ib(lusTbU)g=%USAI9OBMg4n$7p|=5TwOx!VtN&wp9VZT8_7 zA}O%|Y0OQ@RlMtg;hKG|zBjm2esWJKNm@SNe$3HRy{SC-phwiDUp!uIao*`8cm?|$GtIp`TdSdhQ-b&;L0 ze}B!Txe4!V!#AtYm%?AZ^E&-)Ma7~hNqX!395emrZFsxJpy$>90*MdeB5yW(LnWU) z&90S4B?xANbf75bt|Md(Z_i|uL|jJe=(-4s6Xk$-8vVqDiX`zz%1=};9)2UQ)sK-b ztCIh*V9LApTYi0-z^!}^P&Z$CI|<0?44Qa7btqF&_W+W3tNYEqZ!2J#5i0W!5>R(` zZx<07qYwt;s^z>q#!ruL6TV%t;nkZS~ivn&TsQ2 z?v(4oY4e4;o2o(Bdxk6Zn{c#CJ=|;;CgPq$>Bh;=Cr9(kdLw2>**?P-jY*# zTz~mg^>OQ--6ciEP;-UFm#IHD+SB}v z!|wbSLCiwr-iCe<1!(Mb;9M0irD0yH7`92++5E=h^=*tS6C0wDo#wJM)}>CeYX7)! z_a#q#mHa~Uk?Igf@NGh3WaA~^5A1YBVn7;$PCik^j>N71pw~yK3SGH0|NjVhN^vf% zFxnFRmNR#=sV9~&_g)pg^u7o8)M1Ax*`vl+#T>sHve7r6mmI*5w{$|^&@#nOh~EgD zkbpkKYiQ)>^F)Q5t&9$X_vl%|6;-B?z3R`@o|67W&$cyX20KQtZpKdQQJoA(u#mLK zh!wn)d9Bv}Kj$XWKzI0K_D{D@HtA}Ne`K*;)=6IhWGe}rf%artN7amAIDoOHhYw1? z$361O9*-rO&8NEz2qAm5@^W&}gF@w%vYGobIbxh1d|AySNy$-!4VRTqoP z0O>*@_#B3`!fTO3U^2R6#x05RVBRihMAGC{JMGVN&W-NSpNZ3_*vS z2fc~8N0tY^^S9%%X?9Zp|7ZoD#W&uOA2~52x0k+>8vZG17t0=m6>Jq}{5R&_0;0ol z3EPntK%l`%0KgXW@u_qi-)rV2c6#nLcW|%aMzAKQ;g?#*;vK0MyI}7B(cXJU!}a}r z-&&C9L`l?;1krmpI*Cq1C!+V>%jhC{4}xeR(TUzmj244n^v>v=QKme;zwfVH*S(%~ zJ$LT4?zNsja@H)4Gw=P`XTSFQoV`CYvke0^D$Vd*(DdN8c@D*Atb;HeKHNW|Isb;T zUou8jZ2^r@klMdvFWKFFh?PS!ay8E*VCRZ0!&U%Uw%~*c&Rs1V&9iPX_^BTte!-*W z`=dNSUI=M_^LI;N)qso2X_OzAef9!Gazi`|jFN~OO$t=Y`?=Xl6*3xeos0emF1hKs zsq0%|Q9e{&Sv6tdNAs_X=f(Dd5kObCOR+F{7(!2v4qn61Z@r}(nGy{}On~Lct;;UW zJFLZH@J23q*=n1D4`-(kDCRxgdh!~<_FPw|nLPho9Jg&xD5H0i_{3}b^HI)Bx^tt? zZbfMwPMg%~E4N2ek~;edAIXBuqnKU;3QyQ7_i}wNkNn#-uY1@N*iCX=6==Eie%yKq zw{T8>wI*AI@C6;Hm2aAqmA)^RzAKb zM2_ihMnXCwv5vdTEdD>nb6j#VJG?9xHZi=%b_{RJZ+xou2o+0@>PK!m zZI`Wk>GY2xjZIo@zpJ$nEp0aG(n?>pGJS{n#P6k+?O-Hr*)0!w^j+-J;JM<()rsO1 zc7o+4#t8hLO-C%dUbD)YpG@ha*USGHKPYm|{9ZZatEGtG$Y=A!XCYEHBD%FH*Z4)k zT)gnYWG271)1+#@>cn+U!jV+MH$eI9^vdf;nI$}W-U#v0ZQw%=KYD#0h+ZN-`@~;& zx9Mh)XX5r2D@ROXv2pX%hh}b%%;i?hSxWLIJ9XgW4)$xK$#?`Coor1Cw#190%)zpN zkDZFguV~eqWQR5}b&apn$$jE^IO{Tx>OT{Sn9Apk)qGteXh*z3xPp-A7r>!)R<8*b zXzBXrp`<|Yey5@0Cf3O{t#|3CyhBh62JQU$xM4XJ2I(MGE0uj9Cn>@V3REbg`(Ju`?i z3^E|nDjQ86wf zJsO=3JwkVuHc5b1ABssRYUS4fKB4V+?N`-MOpA%bD9(LD8yTD_5}v9Z@=!y+zn2hQ zF4j1Fvrdkl>zP+5{vE_1{=5v{xyz?q=1V5~wTX(0@|r)rkj4$fYAaEXe8Ckk8gG(l zJgnKQHkQNV8n4)l8&~Wt+1u>DnYM~2uJG;R>@04y`wcSBt(LdlX?DrM88s6(QvwR8 zacpqTZopt(bA7j8aZvCpX86!8=dC4hf7jBg21TB*E%<;Nr_{kOInP~s$+WhujH>#O zXu?ORW_rJ|@Wr7{VBBGaR^_v^M{Mm)owAi=51x<*snV4++A?{T$c@V z{h87+QfDkFi5j*T52Rvizk-7|9|x$kMO>_{TyS5aT6y5ezow{%5e|sP50=4==atv- z)}j%Wkd~8%4>`GSbs;czzG3xsKv{bdx^YIwJtL7@jgTwIVbD**>Oy;U6z;9=we7y5 zIeAb&>pCuNABT7@q4$?IPDBEq>quZ4(RwbB+3TCzAHOb{pk5k6IR!>=K-D+EU zP)(Hr@khL0+uv)8={Cg-q3mN#!OG*{)Uyg<87`c~?i`!zZF%t2XZj507?E9X08m+M z!bkMfhx)kXo_P9xfK&s~ym?(8xV@4*s^t33&5G|@KRHgT`Q1%SYCb()o6h^EunZ1O z+No=0?;*P(EyT1kc8$cpP`O4}torHea zAt_yMb33PTA{U->PUbO*Aut%bMlpMX&mf+pG4Je+d z0sDSen==hmuR64ZT&Hd&wa+J9t{{#*R8~G|3+B1Ll|>zl*wI)azVoFs4eu;e-z5Q4 zJb0*`1Eqs1q%kZj1saXGFk(E=9UfUS-aIQGLc-O>I(V6-I`-G4f;<$dN8zF^W$*Lj4=gz z;4c{jpP8O|g*-$#0B_EBAj{6n9SHd8<}mQ4;F7Yw>Xh{b*XSk{G)WC%R5=H(nRC8Q z@yao@H87p3F$2FE%|UXBUIUTvYlz^5I*Ow?6+cmg_Zh-T?D#$N=l{licgcVfq^&ffdOP(MU5x$<$CsxxvO3?L6UWh_av1}VIRAEuk@2aUM)}Nqc37N zO&$Ae@ljfyYe@I+^u|B+J~`5tm|EYm>gzAD65Tj%8AE*8Giu8n=NSAVc?Ng0{>jou zo(i;_qF4*Ft`R zT=ycq_6?N*hSGt>01kxr<-unCq~rKh5+YDsalm|2IXtbCC-8k+iRb2t&z6ec(ib{9 z9b_*z{$CFbbHn{?xVU7ewUNYS@FVk1`i8)hI@OLCymoS6FTB^<+WOZnd}aFnHX$e+ zy3lpbA!D+4W^ZZ@7&-l1r2zO;SXah?N?U|@aR&s<#E((W03|>rN}U@0hx67@_x;9o zE|3lVOw!3q0+0t{A&3&zu!SY^#*~( zC|$4P^WqY9T58q<#@iZm?opayFa4F2nICxgD5$~dQ9#g2v*|M~@ICg?bD)b+@bF-> z?`ZYiGhAbXJ7Qcuk(AS`qwW`Q(;e(a5dey|Ok-rBN?@_o=)kaj-8W!q7K5r6deIjr zoHGetyvnmi+qmr<9;#9C-vIoB3pYNL%5=q^ucHeZDfvBl{R$(GdVZmw^2%@HaoOM% zhG|$197K5qH_}F~;{}LUvZCGvge3eJf(mtX{max&ob_oQdyzC|KvIB1AIwB{J#Cr9 z%f6y~rk$yBy->N}q}p9utKp1PsA$Nsq7!r|H+*!{fGPMIzH)21_!(em_0 zoO69B_ic9ft_cgD-^eNWa@+GkUiTM=c{ah;n`N)>huPwpkr3kEswjdEdm&>Km%}e} zYQwh(^|{OIF{i+G2=p{7yzB@sNpWa-FTMsl3)1K3kx0^H;i=%(S4$54M83FvrQymHYNihbPl)P;^+v8K*h!O{W*pH(nOORR{D#a zkcNG~m$WV$ik)e2ysCBOeYEoFPow@n{bvW4EeDS9ZnG5AfJr>OJD~ZBDzIx+Nv5r* zqZMUFiex8e=M-`=u)Ol;-sz{xpj@;4fAv^(mZ9hO&TO`0p(|}K*Ye(@{jKSWOW)B5 z=mXkzz*1-uFd>ktA%FAJW4&vr_rZ^bq&cqZ#vc zzQ|MFI1-`XK7>zy?i_4N0ZI~y<~eoq?T|wsm>Qx?UjX+UJ*v$e_j_d}H(ZNQ+Q{#5 zDk)dQAYAcU0F+eRsUTsz%IOKJM^XX?T@*iU69fUpuGc|j=k9RWHge(L!MT0}Vr9cu5cXtDAqzDQUWD##x2K@*fd17zq>6&CQ^Q`i+3^lBF8BeMIQ z0CIk?bHEq5cy*-n?@Rul_r$;NjNEOUeHs)QK}1;e$QRS2JW`5AYk8d;3(7;sOT^1= z%6h1e_3pgDwHlV?;!|i#{HwkFMv4~U)c9Ct$+cuXskQVr!coogTTR5{cZDl+uIKSN zr`|r%c>C{QtSOy)hfStK$^mH!NnWFr3`+|<(D*6UHbG*0T+2L54qj09(T%`$o=CkAGQD_!U-vg2msR1^F#6%@tiDw+mFC+6g_C3%}TKbZG z)uflGK|9v1NLgh3eWTS|HN=|Ukn(PZJ1g^!i89LCC}<7J8<;J45I9-a*|>Vd+Dy0L zWUStL>$XH|LOlYjvx^FfWNbY2r+3+EUc2TM@eUSDrJ3RyO;$@*}O!5`C5vdUkMKfehJz^Ee1q1 z*0S{`Ek&pyMSz7Xi(P?f{xbc!Odi-(J0BrFhl%S`748XogPrzOvD)SIZw|8ITvO0i z&sP%8yZDNT&SUYIsa6$cRAKFzO8KcM(T1FHt>A)Pxm8Z)skO?-JuJT*t}3>fc0>R8 z%w^gZx8p_GbJa?Hi0hZ2)osI88vg{}n6|>o)1SVN@0;Hn@RRN=Svqe$Pg)ZDb?AK* zURDzf63Hkh^BZ*c)9p;XRjT}Swtw~}%FKrkh4wl0hM@vd*mE}}-T%)7c7)@9Z;JXq ztL`SSzoZ`oGdz?Z_kw(wdt>-LIy_C2a3d@i$9iqM_ebhn19$4Q{IW6#Ip5JS6w^V8 z*qQA7gw!q_MdkII!?tdS4r+&h5TFCp?qC?!w(Y;OfHEJ4O?*Xx<(Fgr`WMOnXPZce>ae=sw0IrKc2+?tU{R z={+TV6de~E8ym--3Dms(beNmFb3KWgd~e{sBdOyLxnIqB)XMpK3mPbxi#Au6#L*8? zcpFvNi{WDYTh;XXENu3`hg?vs9Ac+Fm98i!dW6w>5wlrz5s{%9iOz{3ic zB{@r*AR=O$w4Y}=RPa?}6uwBe(2yN56`>fIkW|O!e(q4l-<0sukqvpc=alGuIIl>V z;P#{XDlT&cU0m~9nCpF_@rU=>sxoRLlu`omZ}KaJjPDn_5mpTNr zsKqX}7^J~-PeDNXfMU4i*0#NP@GJmZx4qBi&ekIG(@>b`xpT34H6h{y~P9E$Ox z#4|+aM*weW@wOWV}(^SNHQxJ@0*tVGbLSfZdGf{e#zD=RxS@)n-r^$ z7gP7goAe?jhlXfMNr&Z!f1IwflBZ9K6S40IUb^%DSnUE%3uODPygLRI6}+N9Ub!@) z7JsBpqxwvpiA88ba7DC)LgIeTw_U`q7#w}+LRe3j55ZlcO(cfssYOw};G-GOTv zbG@ABppUQG$vm(uTRDd=N#Uj7`+S^Kc9txuV8Em}Acuoz=c!AMI=TcJ0_gMbF=;Fw zY7TLq-dOyVM^Ap^8|&<>w*fEN#W$ML0#_8y2sQ1D$DE%TYM6~9(BBIpsPstnJAaC= zQhoyvm+Cv)pam=BQo^|fg;ql<=mlRXoMb2(9!K2s_F~Nf3jx?^Hl)1cAH-ohsjh&& zERs2n+#9l&1YB$ns?6CP5!I1P1TPZJJ@XDTcyH^c@Y)AEg!nxCDM?6`y~d?*wtQov z5r(i%MVT@Up=__$ zce{6cL@IdOn^4&{jH6)Rs(bl(P`Ws=B1cL)JPpptdkh&@>qm0-C*(c5-pG6%lzdN5 zFyHw?X?JQ;TC%Xuo=#W9t08!^J8428X>_hpcpWa`_F!H-n;3Q=4wicbl>+fPttJbd z^2>@t*cZLU?C}252hBWq!#7Aa<%FBkZ413vdSkIyeTDHy=D6>Dp5~OujYArjh=_9w z$n@UYLg*OwOZUc!r5r{QZrz-!9iN7j_M1RNM#GQ%WE8=(>f$v+eqi?4!74@(f*AF4mrQ(xSCQfj%eAUyisS`0<^F9}T;{ zP1Wm9xxRnn1+Kr`rkdsSxlnS7y^V8r%X`%k3>GLq>0Uu8tz7**`@2wgfvA)k)T9#U!^+NG<9vf@)MrF>kpygA*~=Zx6ON+PFuJu;)&F zfQe_7USQW{HG={?NhYj*g0S$|6&d-AyF~`9X`wC8eP^TtJvIR{x4U@y`;IIra^;kseP~;_CS& zehrCcQ>ddFX`u^&>;fl$W;QI93+4@@sjx3Akq^mC^8lLra4O!_Yi+7qNhs`v; zH5JS$T^<^FBu%p%kW5}h4{u51rfp_2gldpA*Kl7>}u{EcC8AT5;FB4 z;BWCt4?^nFWJ^vjv25dYl3CHUb`le$RDLXDPgI7-prUCp8bKwF@5Fh@6qHi5T&rto zdYcv6={DD|V0ttyws9NrwHNA+<%-gXMdHTui9peY7mquJ$vpAw_N40?%M`^B%vZeyJ=oS<-d6kfPGW^#z75WGB%dx$1P0Ki(RU-5 zwXg&|01ql)4^a|qX5OE;881eqyZx9^N++ft?l#*8f#&nKizTW0iP6~e)0(RIP%+eU z`v$l%&GIKh{gvnwuCB-XGeR!ZA%Lc^f!HPGa_mIdk7tAfB~L@dh}PNwbO_CIH>xjnUPI9CKBN|;fO&kXoD$b}GTXDW$dmw7OU0yOTSfXI#;9V2hCKWs3*Y7XbqG6f|z(;Q&kl&-8Sfm=W zsVlf#ZY~V#^(xp>F2m=#mDyoboj}&wh;>0uPHrfnSYtK$&YgR#)>;^j482dw1v=Ro zQsu*Z;4FY|qGS!$T{?U95tIsT{JELCT@3fPS154L>X(~)<@sYnu%D#YxdMnCHa_Ac zUPxD75Wdf&(7$^H#9h=;i|!kXA`YtB;BIk^qH8#tH#Z5Zn&0gubk*~{{yE2w=V>Jg z+g*$W`o#Aom|sRBMrp%@j~lnC{97{}-&3n_1%)>!JPPZ1f=mG2qBz%Ojw)AX#z&+j zN^`HEZ8iFt!}tMySm(#0R?*Z@`Wn`T};yO1kT+sgo+=^0x-GHI58>k=`A_B5(we_LP{Ndk*e=~_;z@*ha?o?V-qMto6Ge;eeRN50S@;DL#V_%cC)2XT}S3-QZkgMXtvQl+lxh>9hw) z#JIWP^T%Oc@9&ntA97hEh_d9n+(?mKK^l=%Uz}u?4L?^JSsRl3=a)q#TR$#b*PqAq z(#q3irjCqy;ICj=?Z|VqgdXdUhq370frF`nni5N-6)JuHGzp6IxiO!+>K)_G!wZSe zby~cPK0A0$ax>@oLrTD1cKf01RE^`O6MA7Ks<3rStCOSVeh>WQQE`n|2B`D+YN@b< zqr~fh1=Kceek%okj&eX{0@GVU%0No9JbMx?wKddd=8T}1I!}7+Pj%6QLsVk34ImeC zAF*0_h26nAFI+I;+2XEMd7Q*G>Q%!3>m^41w9YE zF5?$LVtnyi^%4W{LvPxh67N_iy6xL4)YVNw23AoKpNlebRJEX}p*XCx%>RrN%>%EJ zF(32H$s_AAfNW(i&cgPj`rU}EVBMKS^^pAix<{YsD(I#NBiMtBsmCkT2OiXZy5MfM zL3%|E-(?udVssKMM;EGZo)wc_!m@|Hy?29`pmlBZ@aHH|5zq>|ZOl$$EAPhz5Bjtm zNYk8)4)GGqkyv(del16bEaq{miCgFFZG>HAGQa;abof)Sa3|_ncdz0I&-8SoW8h)F ziCv}B!9@EnZ3H*ItRH#|=s-m;k`M7|Kqu@nTvg2E-Wt+`JV?*Hx;;J^-3~)aZ~e-O-=_ ziLnT3wY37W@QUAQ=iv;Qz^h;G;ohMOCivgv@?bWlM4@r#v3{4Ktqq0C2w_y(3XR5I zs&R&24(0J#3r_>JPw`q1B^tc`l`xZe$FmS{2U%sKxq`pNSqAmuGA3sNVi&Wl35pkp8-*EwXU(dkhfI9lyn*nIVP;F~ug7}lOI78yxCN=Nb z^9HlNg{y`FgW&ispJ*d353elr2@``?2a9G#Ea*dY0|uEI^63eCs6z&x4_Qlv_wsGx zS715&zS!I_ZL~y{Cj-8OKQ%Emh(>aW516#1e$_nZuQJOMA;w_l9YoLVi&<%SNnS1w z64o2Mdg(UR_kh2YEd7OVF5(?M7#K6$_UkbhQ#653_2T$~SU<1U9nWtu=of2`rdQR4 zzy}33M=4Vub>h!Va7*m5&Iwwxiq}h6Hab4D7r)QrrHX<>Si!P5Fj1D-M6nDSO+LwX zB>yVw&9nE{lkWr%DP@Gw5k{XM($}H6)L&!`?w47L@3tIh?=%x9hR*EIvHh}hKkjph z4R69-ekT6>k%vDYAhr;sZ60c$y~J8F8KmcF-^1IiyR*F6>Gy8XL5E`4%^@BCGJK*bIJ@?1spH) zuL$v5IyKQG7(D|olloo2A7A*yy}O)>ZaG}fSZA_TWvjcEYBhM0cpupat_-qUnb1#N zEGMT*%Ktv`lnx;9A?>pxw#TmLdt-*EXYo?;u*C>bLVg?J`LL#s`K=zF`;>fLVmA*) zlhliNq`BN;I(UICjo1{5a_xa(uVhK7oOWOVd>NHincs8z-2M3u-KAXmSpyO zkoyoj9HK&8UR75(TVOE};yb-&;uBgST|9y-smX)uxBJ!WEpLj{hn zu-5T}TR`=Vo(aY&p0p@{2e$x6Y-YrUOPMP7jSTYz4nEvV-HDX`5g1EB`!uv|5dQ+Z zzi7AmXkRuCzp_sdUjMx(ir!HX&_1-X7O<>!0y#q;N1w%E$SRElKGGk!k_y$&?M< zUt?a1`5q>m0q8M>Jq)qK40oO+;oTJ1KV_#MtyHOKv`lAAxaap}^V!R#NafWXpR1GSgh$A3N==VbbqpCoXm_)UTho`E`by6%+|**Zg#v&M2&V zPr}smP#nR}=bNw~$oP%?^Mn>+cl>-gOg1D`6Kk{cxnz@JLL1Wq88oGzp!Q{duYAju zdk}l|UdZb~2UnH^9VN`;pY=8gMEI?Xslvo9>{1@p^_K&^Q=uyJ{w}!RAEm+Rqq$~Y z46884J+v_O#iu_4=Gn9JAL1#Fq$9@P$w{(zgE^h;Nf{AxVvzwt>4u!-H1%@RvImsX zpYljHn4)b#TRRRYcvUnwFaPCq`5471?%LTBy z=BmxV(qz|dLxWAOI*V1#Nk6*Q@C1?U5z8hn%ch`GuX1HmMDpWtt<~a4HqH>J+s7#v zN&{HJG8FbUyU?;%-SJEYMI3K$-h?=pWU3CEF^ITPkx)KGBRiaGd5CGi(PndIZ*xlj zfF2;5^0e{(tx5WcM_&T0Q^bNLNti-oF~WGRA*&GLZAe;x?(IOe0~V`~wMd(E%78?k zJC7=tZO2($<10P>vJ}oDcT*Knb=M{*_{$Ln)uVN|DGSG-!iN{rp&N@@)l&yDf=1i+UGf(;^| zCloIac*GbJ2kEX3^?jtl5M_=G-wU8)fDMp73*Ul0SY!pt}j~WP6V*@txIBwNl(pxGn4_7;vOu>P-rg4sY5%7RNr8cTu1mT+g#+Ch>s`$$)j}%XvH_`Rk|WU2vP5+ z0oBg>!JjxZjm61^%6`s0S$(pZ+kAVh0H$-5A`axU%GbJjM%(%^&Y8ULE?9iw7`9uL z+{Nh$l89|sEnr{&r#?J0UJzkNo#BSbGtNV9&wq7e%d9u}2yPW-B)VW(xzq+@SO{v? z5Xst{;5k^1OQ?HrNgw%KC|+1I-^|7G0(5c|xqbOwfI-CB{fp|HZ^r!#Wh{>IdCw0; zo)jWm3pg?sK0-=SZ!$ZkXP$pCB`U4Tc_MpS=zvaTtwP9v;^vR80XF(-!!U{i1CXw> zbxZbR$NT8BHzV&V-9^!@T9~=U;GtcFwCinkQcsY^&zi#){FopTeXok#L2vmofVvEp ze&TT&FBMOTWK%*bZ@EjQw)qc0x1D&HQ%iKW0!qpJPNF8F?Y=0*&v`EsczEjZX20vllk}eax0soSS1be*qmdyp@IG_IK_A z9TrEUnV7Lp(VHS2-my$}@k9YmI`_h`56%d|d?~cyi?Jp`1@o7p$HWoCOJTjeCM}-s z<=`w=nP%Y^-ErGC!HPby^ws*vyVf}hV1%g#9r$b0TX%^#Z6|5zrPIm=8eW&hQ0Cd- zsE>NcW@qp)>GEykyvVv06>EQPN)@H}aDG1Y`}0pq0nv+NE8W`yp(;tyC~^M5u%lC> zclW;B-jRwV$&dpWTdDfL5quGeA1@M<>9-^- z1CtG8Xvb{&0@8hI8z=$VtD_I{ zWh&rMr&`3DO8R!$nE$dsFAbLHI6X}A5ZH#qIVm0S1)qeBU*rWG!C!DYOBIAr8_%ai zo3OmqAR-xZJt*r5^J=KDv}#1(0ec-XXQ@j|$OPZg#fB}0x@;WDro3W$&{j%}Kb@PK zP=azz+Y+cD&>HVOq-+V^3-NyQ{2+oAfAVYCtdo?B^HChM)_8HmpRe4jK@{(XkP38D zAGy@4pS1nc>V=u^(S&K#bLI!H?75waqYJ9!ntZksZh?5IMqElM3nPPa_^Y0nqpm~- z=|*$0ifo%{^?G=~eS_lHl77r2Lf4v6A+99Z;mz?K&4aNA7);7*TAZg`vMH@NZ>ATl zL7I?(^3u(n^)q@Syfxzew@r-zi-mbg9^bmuO;6Q%!Tx$-13hEm-YXNAh;QDSdld2= zP=e>UN3l^AJQP-LWLjC6-O9GNC(yB{#EL}}qh!C1Cl^2EB+P@*$I__iVyljA_?Sbw zDtoR{8&j}B{%tf5(QC5k=)}aPgA_hrDj)I*joD{COds(S$91`^w-}1w+Che}N;;B+dKtHL zknBufBeO3qZ4@7yWh9tjalDOC__E?kzXHx5jL?iWT&w31w=X?hRO^*oLS<+_3)l-C z9ZNfMM}O_v?_k+|k^C-8I#9Z4Z|i^}_vd#c&pLvdyGz=ieo*;3W#}XI!(T0J9(Ln6 zr=T$l3YC`rg=hKd4d zHy{BPw{U~yOfnzMBw_;!N!;Qf#m~}~bZQ(I>(?kd{qqH(jEN2>u^T7AuVzM##wwqQ zK#Hiww8s?0v|KA{8<$jQOS+k|MQ{snI znr~!BjAvfdoGg$d;0Q{22S>%85;KVeJW3nfQ;MEa8Aa1A&xksRzLUXzS%reWJHxro zPCX;JpDOk9VbS45MW-fou;&5ig`8gW-4|X8=s7*4$?DF(#K;y~3T&7xd#0xjXb<9j ze!VzDbJ&EV;AUr6KtfeO8zKBY8_!pIsDFd3Uqb7y2z3YLfL)~`4o~kcJ}e5c%psI0bx%A z!TZ^wb&~>Zh)Dnsv)^BljG7ne031ZZ$$%mD><^m(XF zs?_pSs~n;F`V4@w>G)%foN;D$26qD%=hp-QNxQu%MjX#}w^NgEZNiiHeZ#u1ew+;% z-8Wa}qP#y`qOUmFI+uuyj3XWQdv4B%{{qCqzB~?GKsEXv$mhATWclKJG`wi5rEz_` zY4%x2udi)!C5RZ+jV$WWsw^Jk+8Xq?Nw$eN8DaclebxJzM8X^Q<6TDc`F1Mf{fBVC z&95EQNJEk*sT!?~|AI9o(b)~a!}VM4d&dLz%>vH?LzsTg*_^Wn3Ok?eJow?0G8?No zC?~J+{$evA>Ur8HZ|PO7_uo4ddiy%|8ahaHEGt6GaGoJ@EWpDK7}RyPS0OQTNLOTr zs{3=OV5VNSHwDM@Q926!gN_J3mg0&`MUkX(e{)F4$aFdAZammp6e}#K*rD-SI=%^Z z3SrC`#YI1OS5Mo_iq$S22I=^@a1v!{dd$;6P7D9!vb_z12ExjCc+M9Un39(D^}<42 z$ctPbJ~qx+aF4&6-?1C^JbUq+CLg;-DvVaH&FZXoqFFj>Hq**I6~BF$y)AtCT}>4AHgJEnJ#N>@)8j9#Hf%*SjL)8N$*=%es1V<_3LhVauXZ1o|C5lJ;%5*DIH=%ZotHIy24& zDk%v-o$~EO+vMrX*(X}duCrhcH52!QeVa^+JBMfwqBlmCPksv6CLrO}?1qc*e`Pso z2dFDPOw;7!k07wxGdHhm&~SFad>yQB7M8BdQYc9eLP=G4tn2Z=e|xN=_+>WNB@H)b zFnrzYXg%vkwF8||k)cSa`7N*D*_sX^5MNc$ioNB-Y1=Rwb2eoWNd$JT?AY`P|COz{ z9Lx!;_9&m}$_Jh?R!5-ZCjjjrGW_ja{S|dR-QDSRdFmv5*%Gei9Mi?Kb0hy=-NT*krs5h#9+teP!mXS}}Nn9o=# zS@G7CL<#=`-##TO^(66)T<(CZ1SpV(jPJa7gcx_k5OpbYUleG;lf-B+tNxTt*#~Aj zk}PrL{&DkF6Eb+x`pf$P{Ch#mwuV}M%#l=WSvdWQW?^h|V^sG#U#h(Sxp^*uj?q^23kZPRqL`JM1 zhU+4`92}R!KBj!I0`TV|+FC{1Pe($CQcMdcQ@t2&6KHQ$SH$ddMO}VQ18eCC*J$EQ zr>|Gt?PlU#y^WtLL=^_opbE%aeai?SkX!b5RLoP`)oM|rNQp`3=F92xNpAKB7o{Ln zOY2%tdcSH6U0C1k1N}6u@6pBlYl3V~5>f-Df;^+_s?9xeWG8P{E;KlQ=(8YvP>}e9 z&+QPudVmlqcfYQl!a`w(Gc{%`b$eq~44)riOvr~|Zd_nBMI~{!xp1jg;jUs$fi-U7n`+Z(rEq8KdYs>?|CEP720kgZXD?PQ}$?MmK zsan~F-hF64SzpFvh+GNy6vr><+9Bo$m{OlOsj>NKB=^mwlC9nERwlMytLyN_Dek-!jvRN9%%suI-narHiXn4zPhj{y^*owV3+6J^!j*oHGR=TP@MLN zORf7eL!8lV(ZyMrRBvvX=+~TCy3Th?zfUl;d0OPXOLm+c3TC=#Z`Lk93L|3f099PlVzlbv{i8$U3c3 zSRK_xh;m9fx<0Gy-i}JGxtrbm(!!?n1mJL{Cx}*Uo5}38O}C7U5Xmzs(u7$v`|6d+ zZK6D*v~^oHGC?2YSYto(@<7JxA4R{Ze{W{3TfU+ZUsvlI*A6WM4A6o7Rl1-wwV(zq%Vuzdl_%_g_NaEwkdYhDAGLj3dFcCdA&NCVOp+XlaZ7%~ank znCi%>o`54gRsz4WJN5hODkyv5CiH~srIPZehXsh=#>uc@ecrC@5q@tgk-#DL9k)*J zz5tDb1ecG)?peHdTKXo8)~U{TpW(ry6*lepUv2FEQ@LnWTw|$gZ_x5@TiL|L31&uc ziz>UE8R+uXmY~~{QI={h*Cy&HBt_fob-cP4o~u>^Lm`>B61oE4?PAEXgfk*R4E{FA zK3NNl=`FhqKB!Ig?D?|($Ufgr{v%nyZL}-+Yy9LxtWf1Q>|neV@Qb?(73pKYx)7+D z=+mv!yhDcliA!}JzJ%G=vl=3igI6?F?SkkS+LC~NNW`TZHSxKTp%_kpQ7+9X`;?XZ zk~%g_JRN%@dW=-UJoe<2cO;?w(DQ&c!qHpwR*;fKodaP&%>loPqT}&19s2)l^UW@gZ3~dPa z!juDcz{3drM%ij-M`Q^5T8tX=%HeIjQ{@q`**(GGdHYO36=c)UmpQFvQ#XWDjzfTs zG`Naboa8}4^KihI+9#n+VlAZI!kMN#3)VaCOL5ozvUyRc&J6}U$=x?H(Lr164b7F0 zJ`aC1MLoC5+?cX+IpNZsos1juHbc8sXK2f+R>CLc8=SvIcA&p}`*GmZbLN`7^`ePq z_h-@IZ6odg7={9FvYB2F>2{o^RR#S2f0sNMg|nFTDASwu3k(g7m(2FHo`;^Qim17> zBd3{#^E*pUA4iveeBx6a;NxOuZg1(q@XpfO)=7f#sI`-k!PY{8QCC2fTh&F@(#BTF z&&^WXPwln2pS`(=1tUQ6fw+(8T>wW*4>JZIM+YZ&Q6CA$f5a8N+yCn@7bC+zBp&t> zjM9Hq$e^dH!657GX34%)(sM%Gu4)%tO-F(ahSC%f-oBoa;Xa|Ht$D`>gM5ac4`efBW)Z zr~dD*{J*mEAMXEG!v7ugPbI$v_YbZ=xPFVkZ;Ai7>kqEqBJf+{KkoX2>$eE}miUjm z{^0s80>35xa5(;QB2Bza{?Tu0Obb zi@h1#kGuZh`Yi&#CH~{CKe&F2z;B8Fxa$wD-y-l^ z;y>>CgX^~l{FeBSyZ+$%EdswK{^PDcxPFVkZ;Ai7>kqEqBJf+{KkoX2>$eE}miT{k z*MtA8^sscgTi)S)w~pgxqxP>=Aj&W0q+hcnnZr>i6cC#fjg0lfBPuE?y+n~`cyHEU z&!iB)x<~!?%Ss(V8Nt=3$b58tp4vD)0b;G#f)J`7`1m%t{a>hZz)EKyvC$Taso%`D TVebF6xa6h0np~yK+tB|9OTxo9 literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/feed.png b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/feed.png new file mode 100644 index 0000000000000000000000000000000000000000..315c4f4fa62cb720326ba3f54259666ba3999e42 GIT binary patch literal 691 zcmV;k0!;mhP)bpQb1=l6TxbDZwj&S={?7%qx-u`rsG(Zp`-rh=e^=%((1yvsuf5d=&62Zj)Y zH&JviNS_F4_Hj|T(1j4$p-!}kixP9&dB4uv^MveG?dGf%sUCoc2!IFxD6wHRA2^dX zXRVk!-qSfk(jcaUKn#RP48(whfPlJUpApdrA!TQi_4D+fVoM;3I0gZ8{=Xv~Po;geVA+Em9@0Wq2 zr>OTZEGR05L=gf1T;ucCxq6Q6EgJiH@@-lVaAlQyw`jIF^c=&IVnj|95hHbE_cnt| zTzZQ?F4Ne@(bH(~&3nM%m)I@ID{@jJ2qZPjr)jhpe9hViOwH5k&|T#EmmL3(vHeUQ zq^!t^Al6JD;=mHq^Bg?J-8-zG2Od7gZbknG;K9czYjPqG*xjPo0k(c4%lPXTpw(qq z@aGMnxtFS(np+2kC} z7P02O874ZkJH$v#nCUVx$({yDN`IX@o2wyvTD#e`qN`_w5<}$3F+_z1iyEv%?$mbQ(# zwJpuiQJP8?X_`#S8b+U_G6=ziYB!xPAcq{)ZJ0bECH@ zYx#`n8^Wzn^J!4>=q^bltNO15ry?0ecSLkjpT@vlid!jk)Fjf7&)q_V5zGs#3N%6* zbW~7Hg=&P0&~Y(|g>$hC9FL?;ttzPDZbpZu9OLb33^e2;FNTGJxScp1&q4M+y2ntQ z?C(=hpU$3~`Thx0eHwi0x`q+!d5k@|0_WHe%sG3e-s^MM`xM-ig!VcIA7H}X1ot~L zg=MLB4w-Q;Bi!!u2|I+Qb;0{{4Q53YX6+4_aXena{nmt*!YG7ua~`qc>o=?@U?rOU znS7%>klzi*muXnbM6i@4FR@s^8vTjDgy&%J?w?`u>NYMDFa_2%0SQ(qJE<3=<8Bzo zfdU60e*y(^$RF%r$kl)p7=7tlCDa$+J7w>}DU(O#~fk>pYuRvHi1E9^msg{tLeV XM&GIRvfA7%00000NkvXXu0mjf&%8>| literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/pdf.png b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..8f8095e46fa4965700afe1f9d065d8a37b101676 GIT binary patch literal 591 zcmV-V0~O9lw>B8WRlD)Gm}Jrz31u-X&&gn2lvjs=i{7nIaL6v2==uw+8Lcs(8j27 z;|c`rmSv@Lx!heopGP^^Ieb3f=R!%Lpp$}iMS-&P3EJ)s48wrJ_Ni0~k|c47D2nj= z{jS6bt|kFpFf|p5cM`_&0Zh|`rfEp0(}=}lT#(6RpzAsUfxv^LSYX>WlAaN$>)*J5 z0#sE+JRUD8iT9*fz{)_^7@6P&!sEjTcD+I9Z4YjT1`wH@fV{cEvneYGFU%maIEU2s55&K(LixD|{p-uiS@?KNj zk-Go8G$hH6g002ovPDHLkV1hVj1#|!a literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/visited.png b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/visited.png new file mode 100644 index 0000000000000000000000000000000000000000..ebf206def2729dae1fa9e8c5c9e5a95b7176c45b GIT binary patch literal 46990 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/xls.png b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/icons/xls.png new file mode 100644 index 0000000000000000000000000000000000000000..b977d7e52e2446ea01201c5c7209ac3a05f12c9f GIT binary patch literal 663 zcmV;I0%-k-P)^@R5;6x zlTS!gQ5431_q{u#M2 zg&W%y6a}>qj1Z|7Vu&-DW6d~k-n;jnHsjb-q#u0C^W!_5^C=MlKq<8oNCQ6qS00!X z5eI;XP=g!^f}j{hku}E1zZ?XCjE;`p19k(Rh%^AQQ54xysU+ocx$c#f61Z4HnT#3u~FR(3>BnZniMIF4DouI8Hi4u>cAK%EN)5PO(ip3(% zIgBx+QYirR){Z8QwV$9Z(Mpt=L-Or3#bf-G@66}txq0yc*T(zNTBDT0T8rO^JeNbSI-Tzf5!pBioy4NwAN^?iN#{;fH1Jke4Xa`^fR8m z%h6dq%xX)S?7`zae))(Xst^Scp6B8FejQW?RLTM8@0=vnnntuRGBM2dpo>gbCnTD= z^<;=JuqdSf@O>Z8^XdR?s+KEfhDdB_#ahFj^giCtzT(s8kA$AViyTqaAR;KGaLzUU z<=GqA4bRwpX|IG~*x>pZ!@zLr`XQ`od>m(`;jz|M_*1GDO#$7;n74ppb8=eiqh760 x0yt}J1#p`gw$`o!R{d7zU9~!Un@nJV{4bstt4Au+Up@c;002ovPDHLkV1kWhGjjj{ literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/readme.txt b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/readme.txt new file mode 100644 index 0000000..3cb1b2c --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/readme.txt @@ -0,0 +1,18 @@ +Link Icons +* Icons for links based on protocol or file type. + +This is not supported in IE versions < 7. + + +Credits +---------------------------------------------------------------- + +* Marc Morgan +* Olav Bjorkoy [bjorkoy.com] + + +Usage +---------------------------------------------------------------- + +1) Add this line to your HTML: + \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/screen.css b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/screen.css new file mode 100644 index 0000000..6d3d47f --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/link-icons/screen.css @@ -0,0 +1,40 @@ +/* -------------------------------------------------------------- + + link-icons.css + * Icons for links based on protocol or file type. + + See the Readme file in this folder for additional instructions. + +-------------------------------------------------------------- */ + +/* Use this class if a link gets an icon when it shouldn't. */ +body a.noicon { + background:transparent none !important; + padding:0 !important; + margin:0 !important; +} + +/* Make sure the icons are not cut */ +a[href^="http:"], a[href^="mailto:"], a[href^="http:"]:visited, +a[href$=".pdf"], a[href$=".doc"], a[href$=".xls"], a[href$=".rss"], +a[href$=".rdf"], a[href^="aim:"] { + padding:2px 22px 2px 0; + margin:-2px 0; + background-repeat: no-repeat; + background-position: right center; +} + +/* External links */ +a[href^="http:"] { background-image: url(icons/external.png); } +a[href^="mailto:"] { background-image: url(icons/email.png); } +a[href^="http:"]:visited { background-image: url(icons/visited.png); } + +/* Files */ +a[href$=".pdf"] { background-image: url(icons/pdf.png); } +a[href$=".doc"] { background-image: url(icons/doc.png); } +a[href$=".xls"] { background-image: url(icons/xls.png); } + +/* Misc */ +a[href$=".rss"], +a[href$=".rdf"] { background-image: url(icons/feed.png); } +a[href^="aim:"] { background-image: url(icons/im.png); } \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/rtl/readme.txt b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/rtl/readme.txt new file mode 100644 index 0000000..4c46535 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/rtl/readme.txt @@ -0,0 +1,10 @@ +RTL +* Mirrors Blueprint, so it can be used with Right-to-Left languages. + +By Ran Yaniv Hartstein, ranh.co.il + +Usage +---------------------------------------------------------------- + +1) Add this line to your HTML: + \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/rtl/screen.css b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/rtl/screen.css new file mode 100644 index 0000000..7e7ccdb --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/rtl/screen.css @@ -0,0 +1,110 @@ +/* -------------------------------------------------------------- + + rtl.css + * Mirrors Blueprint for left-to-right languages + + By Ran Yaniv Hartstein [ranh.co.il] + +-------------------------------------------------------------- */ + +body .container { direction: rtl; } +body .column, body div.span-1, body div.span-2, body div.span-3, body div.span-4, body div.span-5, body div.span-6, body div.span-7, body div.span-8, body div.span-9, body div.span-10, body div.span-11, body div.span-12, body div.span-13, body div.span-14, body div.span-15, body div.span-16, body div.span-17, body div.span-18, body div.span-19, body div.span-20, body div.span-21, body div.span-22, body div.span-23, body div.span-24 { + float: right; + margin-right: 0; + margin-left: 10px; + text-align:right; +} + +body div.last { margin-left: 0; } +body table .last { padding-left: 0; } + +body .append-1 { padding-right: 0; padding-left: 40px; } +body .append-2 { padding-right: 0; padding-left: 80px; } +body .append-3 { padding-right: 0; padding-left: 120px; } +body .append-4 { padding-right: 0; padding-left: 160px; } +body .append-5 { padding-right: 0; padding-left: 200px; } +body .append-6 { padding-right: 0; padding-left: 240px; } +body .append-7 { padding-right: 0; padding-left: 280px; } +body .append-8 { padding-right: 0; padding-left: 320px; } +body .append-9 { padding-right: 0; padding-left: 360px; } +body .append-10 { padding-right: 0; padding-left: 400px; } +body .append-11 { padding-right: 0; padding-left: 440px; } +body .append-12 { padding-right: 0; padding-left: 480px; } +body .append-13 { padding-right: 0; padding-left: 520px; } +body .append-14 { padding-right: 0; padding-left: 560px; } +body .append-15 { padding-right: 0; padding-left: 600px; } +body .append-16 { padding-right: 0; padding-left: 640px; } +body .append-17 { padding-right: 0; padding-left: 680px; } +body .append-18 { padding-right: 0; padding-left: 720px; } +body .append-19 { padding-right: 0; padding-left: 760px; } +body .append-20 { padding-right: 0; padding-left: 800px; } +body .append-21 { padding-right: 0; padding-left: 840px; } +body .append-22 { padding-right: 0; padding-left: 880px; } +body .append-23 { padding-right: 0; padding-left: 920px; } + +body .prepend-1 { padding-left: 0; padding-right: 40px; } +body .prepend-2 { padding-left: 0; padding-right: 80px; } +body .prepend-3 { padding-left: 0; padding-right: 120px; } +body .prepend-4 { padding-left: 0; padding-right: 160px; } +body .prepend-5 { padding-left: 0; padding-right: 200px; } +body .prepend-6 { padding-left: 0; padding-right: 240px; } +body .prepend-7 { padding-left: 0; padding-right: 280px; } +body .prepend-8 { padding-left: 0; padding-right: 320px; } +body .prepend-9 { padding-left: 0; padding-right: 360px; } +body .prepend-10 { padding-left: 0; padding-right: 400px; } +body .prepend-11 { padding-left: 0; padding-right: 440px; } +body .prepend-12 { padding-left: 0; padding-right: 480px; } +body .prepend-13 { padding-left: 0; padding-right: 520px; } +body .prepend-14 { padding-left: 0; padding-right: 560px; } +body .prepend-15 { padding-left: 0; padding-right: 600px; } +body .prepend-16 { padding-left: 0; padding-right: 640px; } +body .prepend-17 { padding-left: 0; padding-right: 680px; } +body .prepend-18 { padding-left: 0; padding-right: 720px; } +body .prepend-19 { padding-left: 0; padding-right: 760px; } +body .prepend-20 { padding-left: 0; padding-right: 800px; } +body .prepend-21 { padding-left: 0; padding-right: 840px; } +body .prepend-22 { padding-left: 0; padding-right: 880px; } +body .prepend-23 { padding-left: 0; padding-right: 920px; } + +body .border { + padding-right: 0; + padding-left: 4px; + margin-right: 0; + margin-left: 5px; + border-right: none; + border-left: 1px solid #eee; +} + +body .colborder { + padding-right: 0; + padding-left: 24px; + margin-right: 0; + margin-left: 25px; + border-right: none; + border-left: 1px solid #eee; +} + +body .pull-1 { margin-left: 0; margin-right: -40px; } +body .pull-2 { margin-left: 0; margin-right: -80px; } +body .pull-3 { margin-left: 0; margin-right: -120px; } +body .pull-4 { margin-left: 0; margin-right: -160px; } + +body .push-0 { margin: 0 18px 0 0; } +body .push-1 { margin: 0 18px 0 -40px; } +body .push-2 { margin: 0 18px 0 -80px; } +body .push-3 { margin: 0 18px 0 -120px; } +body .push-4 { margin: 0 18px 0 -160px; } +body .push-0, body .push-1, body .push-2, +body .push-3, body .push-4 { float: left; } + + +/* Typography with RTL support */ +body h1,body h2,body h3, +body h4,body h5,body h6 { font-family: Arial, sans-serif; } +html body { font-family: Arial, sans-serif; } +body pre,body code,body tt { font-family: monospace; } + +/* Mirror floats and margins on typographic elements */ +body p img { float: right; margin: 1.5em 0 1.5em 1.5em; } +body dd, body ul, body ol { margin-left: 0; margin-right: 1.5em;} +body td, body th { text-align:right; } \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/AUTHORS.textile b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/AUTHORS.textile new file mode 100644 index 0000000..2b009ba --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/AUTHORS.textile @@ -0,0 +1,3 @@ +h1. Blueprint CSS Tabs Plugin Authors and Contributors + +* "Christian Montoya":http://christianmontoya.com \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/README.textile b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/README.textile new file mode 100644 index 0000000..604837b --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/README.textile @@ -0,0 +1,42 @@ +h1. Blueprint CSS Tabs Plugin Readme + +This plugin adds a simple and flexible set of horizontal tabs to Blueprint. + +h2. Usage: + +# Upload the screen.css file to a new directory on your server (preferably tabsplugin/) +# Include the plugin file in the @@ of your webpage. + +# Add the class @"tabs"@ to your list. An example: +
+
+
+ +h2. More options: + +You can add a label to your list by adding the class @"label"@ to the first item. This item should not have a link in it. +
+
+
+ +You can mark the currently selected item with the class @"selected"@. +
+
+
+ +h2. Demo: + +View a demo at "blueprintcss.org":http://blueprintcss.org/demos/tabs.html \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/screen.css b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/screen.css new file mode 100644 index 0000000..c95a0a0 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/plugins/tabs/screen.css @@ -0,0 +1,63 @@ +/* ----------------------------------------------------------------------- + + + Tabs Plugin 0.1 for the Blueprint CSS Framework + http://blueprintcss.org + + * Copyright (c) 2008-Present. Refer to the main Blueprint license. + * See README for instructions on how to use this plugin. + * For credits and origins, see AUTHORS. + +----------------------------------------------------------------------- */ + +.tabs { + border-bottom:1px solid #ccc; + height:1%; /* fixing IE 6 */ + margin:0 0 .75em 0; + min-height:auto; + overflow:auto; +} +.tabs li { + border:1px solid #ccc; + border-bottom:none; + float:left; + line-height:1.5; + list-style-type:none; + margin:0 .25em 0 0; + padding:0; +} +.tabs li a { + background:#ddd; + border:1px solid #eee; + border-bottom:none; + color:#222; + cursor:pointer; + display:block; + float:left; + font-weight:bold; + padding:.15em .33em .25em .33em; +} +.tabs li a.selected { + background:#666; + border:1px solid #666; + border-bottom:none; + color:#fff; + cursor:default; +} +.tabs li a, .tabs li a:focus, .tabs li a:hover { + text-decoration:none; +} +.tabs li a:focus, .tabs li a:hover { + color:#555; + outline:none; +} +.tabs li a.selected:focus, .tabs li a.selected:hover { + color:#fafafa; +} +.tabs li.label { + border:none; + font-weight:bold; + line-height:1.5; + margin-right:.5em; + padding:.25em .33em .25em .33em; +} diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/print.css b/pyulib/src/uapps/tasks/httpd/static/blueprint/print.css new file mode 100644 index 0000000..fdb8220 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/print.css @@ -0,0 +1,29 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 0.9 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* print.css */ +body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;} +.container {background:none;} +hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;} +hr.space {background:#fff;color:#fff;visibility:hidden;} +h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;} +code {font:.9em "Courier New", Monaco, Courier, monospace;} +a img {border:none;} +p img.top {margin-top:0;} +blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;} +.small {font-size:.9em;} +.large {font-size:1.1em;} +.quiet {color:#999;} +.hide {display:none;} +a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;} +a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;} \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/screen.css b/pyulib/src/uapps/tasks/httpd/static/blueprint/screen.css new file mode 100644 index 0000000..2c2b328 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/blueprint/screen.css @@ -0,0 +1,257 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 0.9 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* reset.css */ +html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} +body {line-height:1.5;} +table {border-collapse:separate;border-spacing:0;} +caption, th, td {text-align:left;font-weight:normal;} +table, td, th {vertical-align:middle;} +blockquote:before, blockquote:after, q:before, q:after {content:"";} +blockquote, q {quotes:"" "";} +a img {border:none;} + +/* typography.css */ +html {font-size:100.01%;} +body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;} +h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;} +h1 {font-size:3em;line-height:1;margin-bottom:0.5em;} +h2 {font-size:2em;margin-bottom:0.75em;} +h3 {font-size:1.5em;line-height:1;margin-bottom:1em;} +h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;} +h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;} +h6 {font-size:1em;font-weight:bold;} +h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;} +p {margin:0 0 1.5em;} +p img.left {float:left;margin:1.5em 1.5em 1.5em 0;padding:0;} +p img.right {float:right;margin:1.5em 0 1.5em 1.5em;} +a:focus, a:hover {color:#000;} +a {color:#009;text-decoration:underline;} +blockquote {margin:1.5em;color:#666;font-style:italic;} +strong {font-weight:bold;} +em, dfn {font-style:italic;} +dfn {font-weight:bold;} +sup, sub {line-height:0;} +abbr, acronym {border-bottom:1px dotted #666;} +address {margin:0 0 1.5em;font-style:italic;} +del {color:#666;} +pre {margin:1.5em 0;white-space:pre;} +pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;} +li ul, li ol {margin:0;} +ul, ol {margin:0 1.5em 1.5em 0;padding-left:3.333em;} +ul {list-style-type:disc;} +ol {list-style-type:decimal;} +dl {margin:0 0 1.5em 0;} +dl dt {font-weight:bold;} +dd {margin-left:1.5em;} +table {margin-bottom:1.4em;width:100%;} +th {font-weight:bold;} +thead th {background:#c3d9ff;} +th, td, caption {padding:4px 10px 4px 5px;} +tr.even td {background:#e5ecf9;} +tfoot {font-style:italic;} +caption {background:#eee;} +.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;} +.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;} +.hide {display:none;} +.quiet {color:#666;} +.loud {color:#000;} +.highlight {background:#ff0;} +.added {background:#060;color:#fff;} +.removed {background:#900;color:#fff;} +.first {margin-left:0;padding-left:0;} +.last {margin-right:0;padding-right:0;} +.top {margin-top:0;padding-top:0;} +.bottom {margin-bottom:0;padding-bottom:0;} + +/* forms.css */ +label {font-weight:bold;} +fieldset {padding:1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;} +legend {font-weight:bold;font-size:1.2em;} +input[type=text], input[type=password], input.text, input.title, textarea, select {background-color:#fff;border:1px solid #bbb;} +input[type=text]:focus, input[type=password]:focus, input.text:focus, input.title:focus, textarea:focus, select:focus {border-color:#666;} +input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;} +input.text, input.title {width:300px;padding:5px;} +input.title {font-size:1.5em;} +textarea {width:390px;height:250px;padding:5px;} +input[type=checkbox], input[type=radio], input.checkbox, input.radio {position:relative;top:.25em;} +form.inline {line-height:3;} +form.inline p {margin-bottom:0;} +.error, .notice, .success {padding:.8em;margin-bottom:1em;border:2px solid #ddd;} +.error {background:#FBE3E4;color:#8a1f11;border-color:#FBC2C4;} +.notice {background:#FFF6BF;color:#514721;border-color:#FFD324;} +.success {background:#E6EFC2;color:#264409;border-color:#C6D880;} +.error a {color:#8a1f11;} +.notice a {color:#514721;} +.success a {color:#264409;} + +/* grid.css */ +.container {width:950px;margin:0 auto;} +.showgrid {background:url(src/grid.png);} +.column, div.span-1, div.span-2, div.span-3, div.span-4, div.span-5, div.span-6, div.span-7, div.span-8, div.span-9, div.span-10, div.span-11, div.span-12, div.span-13, div.span-14, div.span-15, div.span-16, div.span-17, div.span-18, div.span-19, div.span-20, div.span-21, div.span-22, div.span-23, div.span-24 {float:left;margin-right:10px;} +.last, div.last {margin-right:0;} +.span-1 {width:30px;} +.span-2 {width:70px;} +.span-3 {width:110px;} +.span-4 {width:150px;} +.span-5 {width:190px;} +.span-6 {width:230px;} +.span-7 {width:270px;} +.span-8 {width:310px;} +.span-9 {width:350px;} +.span-10 {width:390px;} +.span-11 {width:430px;} +.span-12 {width:470px;} +.span-13 {width:510px;} +.span-14 {width:550px;} +.span-15 {width:590px;} +.span-16 {width:630px;} +.span-17 {width:670px;} +.span-18 {width:710px;} +.span-19 {width:750px;} +.span-20 {width:790px;} +.span-21 {width:830px;} +.span-22 {width:870px;} +.span-23 {width:910px;} +.span-24, div.span-24 {width:950px;margin-right:0;} +input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px!important;border-right-width:1px!important;padding-left:5px!important;padding-right:5px!important;} +input.span-1, textarea.span-1 {width:18px!important;} +input.span-2, textarea.span-2 {width:58px!important;} +input.span-3, textarea.span-3 {width:98px!important;} +input.span-4, textarea.span-4 {width:138px!important;} +input.span-5, textarea.span-5 {width:178px!important;} +input.span-6, textarea.span-6 {width:218px!important;} +input.span-7, textarea.span-7 {width:258px!important;} +input.span-8, textarea.span-8 {width:298px!important;} +input.span-9, textarea.span-9 {width:338px!important;} +input.span-10, textarea.span-10 {width:378px!important;} +input.span-11, textarea.span-11 {width:418px!important;} +input.span-12, textarea.span-12 {width:458px!important;} +input.span-13, textarea.span-13 {width:498px!important;} +input.span-14, textarea.span-14 {width:538px!important;} +input.span-15, textarea.span-15 {width:578px!important;} +input.span-16, textarea.span-16 {width:618px!important;} +input.span-17, textarea.span-17 {width:658px!important;} +input.span-18, textarea.span-18 {width:698px!important;} +input.span-19, textarea.span-19 {width:738px!important;} +input.span-20, textarea.span-20 {width:778px!important;} +input.span-21, textarea.span-21 {width:818px!important;} +input.span-22, textarea.span-22 {width:858px!important;} +input.span-23, textarea.span-23 {width:898px!important;} +input.span-24, textarea.span-24 {width:938px!important;} +.append-1 {padding-right:40px;} +.append-2 {padding-right:80px;} +.append-3 {padding-right:120px;} +.append-4 {padding-right:160px;} +.append-5 {padding-right:200px;} +.append-6 {padding-right:240px;} +.append-7 {padding-right:280px;} +.append-8 {padding-right:320px;} +.append-9 {padding-right:360px;} +.append-10 {padding-right:400px;} +.append-11 {padding-right:440px;} +.append-12 {padding-right:480px;} +.append-13 {padding-right:520px;} +.append-14 {padding-right:560px;} +.append-15 {padding-right:600px;} +.append-16 {padding-right:640px;} +.append-17 {padding-right:680px;} +.append-18 {padding-right:720px;} +.append-19 {padding-right:760px;} +.append-20 {padding-right:800px;} +.append-21 {padding-right:840px;} +.append-22 {padding-right:880px;} +.append-23 {padding-right:920px;} +.prepend-1 {padding-left:40px;} +.prepend-2 {padding-left:80px;} +.prepend-3 {padding-left:120px;} +.prepend-4 {padding-left:160px;} +.prepend-5 {padding-left:200px;} +.prepend-6 {padding-left:240px;} +.prepend-7 {padding-left:280px;} +.prepend-8 {padding-left:320px;} +.prepend-9 {padding-left:360px;} +.prepend-10 {padding-left:400px;} +.prepend-11 {padding-left:440px;} +.prepend-12 {padding-left:480px;} +.prepend-13 {padding-left:520px;} +.prepend-14 {padding-left:560px;} +.prepend-15 {padding-left:600px;} +.prepend-16 {padding-left:640px;} +.prepend-17 {padding-left:680px;} +.prepend-18 {padding-left:720px;} +.prepend-19 {padding-left:760px;} +.prepend-20 {padding-left:800px;} +.prepend-21 {padding-left:840px;} +.prepend-22 {padding-left:880px;} +.prepend-23 {padding-left:920px;} +div.border {padding-right:4px;margin-right:5px;border-right:1px solid #eee;} +div.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #eee;} +.pull-1 {margin-left:-40px;} +.pull-2 {margin-left:-80px;} +.pull-3 {margin-left:-120px;} +.pull-4 {margin-left:-160px;} +.pull-5 {margin-left:-200px;} +.pull-6 {margin-left:-240px;} +.pull-7 {margin-left:-280px;} +.pull-8 {margin-left:-320px;} +.pull-9 {margin-left:-360px;} +.pull-10 {margin-left:-400px;} +.pull-11 {margin-left:-440px;} +.pull-12 {margin-left:-480px;} +.pull-13 {margin-left:-520px;} +.pull-14 {margin-left:-560px;} +.pull-15 {margin-left:-600px;} +.pull-16 {margin-left:-640px;} +.pull-17 {margin-left:-680px;} +.pull-18 {margin-left:-720px;} +.pull-19 {margin-left:-760px;} +.pull-20 {margin-left:-800px;} +.pull-21 {margin-left:-840px;} +.pull-22 {margin-left:-880px;} +.pull-23 {margin-left:-920px;} +.pull-24 {margin-left:-960px;} +.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;} +.push-1 {margin:0 -40px 1.5em 40px;} +.push-2 {margin:0 -80px 1.5em 80px;} +.push-3 {margin:0 -120px 1.5em 120px;} +.push-4 {margin:0 -160px 1.5em 160px;} +.push-5 {margin:0 -200px 1.5em 200px;} +.push-6 {margin:0 -240px 1.5em 240px;} +.push-7 {margin:0 -280px 1.5em 280px;} +.push-8 {margin:0 -320px 1.5em 320px;} +.push-9 {margin:0 -360px 1.5em 360px;} +.push-10 {margin:0 -400px 1.5em 400px;} +.push-11 {margin:0 -440px 1.5em 440px;} +.push-12 {margin:0 -480px 1.5em 480px;} +.push-13 {margin:0 -520px 1.5em 520px;} +.push-14 {margin:0 -560px 1.5em 560px;} +.push-15 {margin:0 -600px 1.5em 600px;} +.push-16 {margin:0 -640px 1.5em 640px;} +.push-17 {margin:0 -680px 1.5em 680px;} +.push-18 {margin:0 -720px 1.5em 720px;} +.push-19 {margin:0 -760px 1.5em 760px;} +.push-20 {margin:0 -800px 1.5em 800px;} +.push-21 {margin:0 -840px 1.5em 840px;} +.push-22 {margin:0 -880px 1.5em 880px;} +.push-23 {margin:0 -920px 1.5em 920px;} +.push-24 {margin:0 -960px 1.5em 960px;} +.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:right;position:relative;} +.prepend-top {margin-top:1.5em;} +.append-bottom {margin-bottom:1.5em;} +.box {padding:1.5em;margin-bottom:1.5em;background:#E5ECF9;} +hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:.1em;margin:0 0 1.45em;border:none;} +hr.space {background:#fff;color:#fff;visibility:hidden;} +.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;} +.clearfix, .container {display:block;} +.clear {clear:both;} \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/blueprint/src/grid.png b/pyulib/src/uapps/tasks/httpd/static/blueprint/src/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..b7539f672bd43f6812a54df5dd42ba33b20b9b78 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<>!3HEX<>xE|Qfx`y?k)`fL2$v|<&%LToCO|{ z#X#BvjNMLV+W{Glo-U3d9>YC#XH~G^;0osUz08pk^?RiSy5sjM`N=Z)ZDzmUBEZ4{G>pO1)z4*}Q$iB}{R}RN literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/bpsuppl.css b/pyulib/src/uapps/tasks/httpd/static/bpsuppl.css new file mode 100644 index 0000000..5700a63 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/bpsuppl.css @@ -0,0 +1,27 @@ +/* -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +*/ +@CHARSET "utf-8"; + +.grid { border-collapse: collapse; } +.grid td, .grid th { border: 1px solid black; } +.noborder { border: none; } +.auto { width: auto; } +.left { text-align: left; } +.center { text-align: center; } +.right { text-align: right; } +.top { vertical-align: top; } +.bottom { vertical-align: bottom; } + +.hidden { visibility: hidden; } +.collapsed { visibility: collapse; } + +.bold { font-weight: bold; } +.italic { font-style: italic; } + +@media all { +.noprint {} +} + +@media print { +.noprint { display: none; } +} diff --git a/pyulib/src/uapps/tasks/httpd/static/clock-samples.html b/pyulib/src/uapps/tasks/httpd/static/clock-samples.html new file mode 100644 index 0000000..f0edc61 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/clock-samples.html @@ -0,0 +1,28 @@ + + + + + + +clock samples + + +

clock samples

+

Utiliser <span class="icon">...</span>
+ou <div class="icon">...</div>
+avec icon parmi les valeurs suivantes:

+clock_add
+clock_base
+clock_delete
+clock_edit
+clock_error
+clock_go
+clock_link
+clock_pause
+clock_play
+clock_red
+clock_start
+clock_stop
+ + diff --git a/pyulib/src/uapps/tasks/httpd/static/clock.css b/pyulib/src/uapps/tasks/httpd/static/clock.css new file mode 100644 index 0000000..5676e2f --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/clock.css @@ -0,0 +1,17 @@ +.clock_add, .clock_base, .clock_delete, .clock_edit, .clock_error, .clock_go, .clock_link, .clock_pause, .clock_play, .clock_red, .clock_start, .clock_stop { + padding-left: 18px; + min-height: 16px; + background-repeat: no-repeat +} +.clock_add { background-image: url(clock/add.png); } +.clock_base { background-image: url(clock/base.png); } +.clock_delete { background-image: url(clock/delete.png); } +.clock_edit { background-image: url(clock/edit.png); } +.clock_error { background-image: url(clock/error.png); } +.clock_go { background-image: url(clock/go.png); } +.clock_link { background-image: url(clock/link.png); } +.clock_pause { background-image: url(clock/pause.png); } +.clock_play { background-image: url(clock/play.png); } +.clock_red { background-image: url(clock/red.png); } +.clock_start { background-image: url(clock/start.png); } +.clock_stop { background-image: url(clock/stop.png); } diff --git a/pyulib/src/uapps/tasks/httpd/static/clock/add.png b/pyulib/src/uapps/tasks/httpd/static/clock/add.png new file mode 100644 index 0000000000000000000000000000000000000000..598b839b818bd0178517bdd88669bd8f9897a5f6 GIT binary patch literal 925 zcmV;O17iG%P) z*2+1JmbP`(+45!1rMYIVT-lbJZoAI(`tt9bOD*fb@80J==lTE7xzD|a0l*AYFp~&k zf{E~sFq!5jLUJ>H&l?CKfAUPfk_jTD4Kzf3O3s;#w&RlJVi{*CUfY6t5x z9&-)0C%Z|#fiPPza27L3Uc>%x!;T8{*2nZWD#E$cS{H6NIB~z_y8w-Euw7_TEcJWL z5w}EM*%qO55kLhc34lj!~dQuY~AMG-7XT$DS+eu_-Cm7BGl7^H985{2U4Q^hi}$&`TQD z#{<4G0*OoO9z#+Z1NHk%wDf2odvzSUWeX7+H&ZGYtcuTCMrd1qp&X)Y7FQJ=FxvXT zk$!M~9x-W~P-ad?S$l@?g``zSAgek`4Sojx^%X~?M~|fz`q!O%h6B=95Zz*tc(WEF zSq-)%EkWh`TudCQ)=X;f_ps>o`UtbxjA|`Ax+*Rg%TLG=nxGU|c_J9sGzXEQi5A2u zFQfn6fm*GePT;IoD^%s>?!|kPB?}^xbObkL>7j@(JEe1RU2|AincnGhpa%s71yczf zgki$`|Bo_-6sLy8%^INFUVSond~L#Zkp%;s8}r$h6cUMpt8KZ&`memftw9r2BH zXZp!+CFaEnJk?xA$eKJbHrNPn>;Axd{TeCT;zL2Z2lqOE#$fse--k~9GCAnb$WrIY zUzF=05;%7ScRp;-^ba=g4+!_yjOVxZHrAy=-Qy;3W{V_ws3i3@rj zN6Uq**d4Ifp5hMksmBf!JrmJQC(GkeNV1GOW##ZtQFwF|H80+P{}RL%{YNk_%ft(I z#;Cz`v~ti@ZP83cwQ@*mu2Dwn=d>z@4RV(M%#h6&LDo!N4vuc zo|;?G=MQ0U0&vMd?WX`v6t{tSSteePbgELlMO}xKy8^QHBD{2ALkbX%5TpZC;3a)d zD?(MwgLzpdUa8!vFvP literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/clock/delete.png b/pyulib/src/uapps/tasks/httpd/static/clock/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..8bf9efe4ba211eb38d4ad50130ab13f4a2f9e17b GIT binary patch literal 952 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/clock/link.png b/pyulib/src/uapps/tasks/httpd/static/clock/link.png new file mode 100644 index 0000000000000000000000000000000000000000..481cf04c12df9c3a81e58f2b660566059d55eae5 GIT binary patch literal 961 zcmV;y13vtTP)=R~W}{mY5~mvTuu@_L=O9S(e2Z7eNN1X^TnY65PZkMq2E!Myv#IPD|MoT56%$ ziZCq#3bD0~B5HgoEtjrXkwUE?RZ5qlvz1m_+VWC_|IRsa&2I9$dG2$b|L@#;aybB8 z>K69_K}qlu9uYpI{r^LH8{enx)0?|Yg zbBPrEp#-|`O~W`8#MK*3@b+w3o^1crL$W#SAdX+Wsj0U3(C!SvM0TUs+jtz$U%kc> zsVT{I;C%ly42~B#W#aKuhwC?X2K2VCZPZb9&4=@M{Fo#=r&rgJOsDaOu>%nzT3B9& zyj@u+eBlL!%m_fgqlS6R2;{u%7BnBYsdc26A(AKW2jbs!pXq zc^yaEhH$7_i~zoPN$R2h(sba6MQ2#d`+0xF4aI$ zRRfRDhZCpkQCwUMi9`auUXR+^T2>21B8c}NN>YhJp@?Vy_05u|a^YS{MyaZ2f}_ng z ziy5>oEG$H6Y3U;>C@(LcXW{<-{?7=>fq?=nceM9W+x?0x=n};ZJH45VhjX-Vl@c$LMez8BK{Fk5J9h%Toeig^+qC6 z^g__y1W_;*DNWEus)9z4+SUg1)0oC4joIC7vYAb0XU;b}`+2c3_Q1J(AKvr697Ai3 zH*T1~Y2YLvK!56l7Va8v+-L?a0RixG>V%#HFp6K30Nzcsz4YRq_Sf$57eC9-uDbJI z+2<=j_l|yQp!}SfX=};ElTC)qzVjC_&~Q>O8@VqyWgQuO(>nBgYiV(G^Y-#w{QK4m zj+2b2OH={yLlfnD9M2Av4ng#OU5KV3IF5B}2ejooI)NtPq- z?`))DuZhNB!;ScDc8#zuMrz|c_j;p}*q)wF>e35_)|$7^U3@>*aQLl`W`_pRJbS#I zqU%!deA-%D0H|pAe0+hbqsiu1DR}YsK7QqqQzpP_rS-w8hU%8$zOE+96-^?MVB))P zDY-782$v>)Ak(!UH}B({`*2nAQGhsr_+EEK8MsoVsi_W$G%4HW&e9SSmw#gFkLx_~ zVS6n{=fPHP-MB_}Wreg9iQQRe ze;78NIdl1Ua|bY0+A3qjt1M>AM1~2bMehTB#7u)sCPPphzK4|c^OQp@1yvu|8^`1Rql9jVO^rWPyq`j(K*<%v~I4%{UU zM2JGsN-0BY{XYQU!&i^ptgo-P^Mzt`dh}*&#;`GC7Eh;ZfG`X(tBR_Q43XNODWz(F zv9YlH Bw}b!y literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/clock/play.png b/pyulib/src/uapps/tasks/httpd/static/clock/play.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4ebc850a94b2c8c59502f38230ac54f164dc7c GIT binary patch literal 943 zcmV;g15o^lP)GE3cNY&!T;=j^f;gxRLSB zG&}X{iKV^(TLqKq((nH^HB^n^chQay$`D@KFoIjnHWarF`VjOrt_N+()gGrw;^Es) zekWfGF_*F4W6rycY4)M|Xov1W$d9~teRyOXfP(u9xvB$sk2`Shbq^GVew4gr@kzaP zX59LCdg%EwGkF7;F9pnouEP1|#-f#;Y_{cdNG`6*NqK5QVM8yJbeph7olboIHi!w*0{lB+uni}?Wrdw+^I36lWz9>_zB+Q!R?Y#*eJ+7IfA?SK}c<`81b)DS%WfNkkdw7agekd(LJt;LA} zdbHK;#S3jCdh9L?O#>;7i74;R$JJH|5^G}+nRTnh9}srxzBoes97%U|kSfg>rh$!m z0rw0++Cc#)*?5$g^H5Ak8dDIh+>J>2=C!_n&;)r9(Xr)x6+{IbvXosg^g6)PJa~^6 zM$(5Xqmkbs!zIIM{D^?Dr7 zWwB4O8@cTnIMbzn`GdXciH?#bSZFs>;4sc(r;{{_Ow*WTd* Ro>>3@002ovPDHLkV1hPn!1DkA literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/clock/red.png b/pyulib/src/uapps/tasks/httpd/static/clock/red.png new file mode 100644 index 0000000000000000000000000000000000000000..2842cc3386709236ba7608965c716054c08ecfe0 GIT binary patch literal 889 zcmV-<1BU#GP)Q5?pHNP6ih=t5yPA=98JOh~fJLk|&ANKv*VsNrUUhFJuyu9hv;ma*PeD|1sb z>Y+6B(ON1k?Ls1ni`{d>-PzgMnb~o6oZ0z0Gv!$Q;deOv&;R@2e1}5;prXCh96}{w zm@q_`r}zJc^cMd)A0#Bt^eOfsl|Z->eQlYT4n2kVDmx(DT>U~VF7hfxg^Xe?V5al) zynOxgXX2ZJ(A21nA@hTTM8hC(k2=Q<9`(zUz2IM1*zk>|g6OOU-+UcxM-$j~H@L>D z;4hRR%ez)LUuWzl@>xPK8D!k za&Y(WfN467V8_O|^!>g%*_GlW+GOfeGnM(-o#2~rEfTrgwGbb85N*GO*u-}z@-)<# z4C&if2(B84Ep?b|IE0Djq}b=Wx$lTJ`FB94jyR)EJNQR;5p8LNUaG2srfJNS7LS8- zTtuY(CPJmTV7G6%Xk?%-YEhw=YpOxaQI4sPUrbSJYb#_~HuimeePG`WLpqTU#!&?> zEyHUX=t;9}ZfJSgh)*k~f!%ILcXu}`Dk?BEGz5WRq3y^3?Y@r4hIGa>AkW0GcN(D` z+6P678DdG2;B-0-@yN)C(c>m*XqjmUHj)=g-85?;=j3=JwU-eqIgaq#VPjZP6buXu zKoA5olfaXHNQbjPSJ@C=vwrppvFsz2eEC^0ogT1f$`DuM_`mcXW3}REYYRfTxnNeM zR+ex@MR}N5`OPnn85Thj3{Lf|nei9p{NS07HJ>o$YpV6xvc)e@INJ2$H@@Gz3$kKqQ*@0>1d54~9r$ z6kZI8DM~a^9!v-(E=EmDl#;3mr3FRM(*5buZgyL`KXhk*%FOKi?2LD|m3Wiyp5&bK zeYxk{b12KQAYGzp04E6c6Q~5c+>HG(4~p0NE?>3gumo@9fE4>g6} zYFAQU7vkAU@;m;bLHyx${>-raD%~IS_cNT2QsW=rCB9+reru7+U%+wa#@CNIJ6{V_ zQ;~9dF~M93{3Hlm9WK^vuoaz#rbrmd4a;3y_Q6rFN^~xlrk#cfA zAzTZ7D}+26S``KAf(R)oLa0jcdz$c9+i5Uz&$0SjZK!B*vWj{EflmcK4Ar0?(Qx!Ccj_ zt;YwMWV0-b@n3#Ml?dsoip%4_qQB=6R0;|dx&;-D+i7zd`%)DtY-kiJstKRZ1+{L$ z>5wWH3JQJ7Nnr;R{okP5y{gs|kj_hj2XMhXQtoC7FZbpa^;G-wKsgxCH@sOVb#5 zaT{7TJcd%a3d^M-o|(pSk#>=B0lh9TQ(cuoF(y(u8FhQEXeau%Z^lyteQ5FeF%cif z%UfT;a()TzZ5`<9ywB}BGc-uAm++fit5{Ppbt?~+1Kds)D{K@D@5F5JZ|J5BP1A7X z>9>*2UB?st4q7$zvuiJ?Nu!nGax|7HB45hmTqJ??y)ts8C5WnsG6Bu52^hMLcLvVj zc4`_mUF&TKTpc<3r(Tmsy4_`YCRN02W)a_v&ftN>L8?`u8wxVzG)$9BCPm@o)q_^J zeLGY`O)&p0%cpNXH|Ozq_`*u5ej+mGh*3P6^L=n`xPwrj3kl^9I2;a~nmmM1>+`rV zceAGH`mx3!jg5^xN;DRFdwYdgJT884;reHF(|W;PruNNQ*VbofF7$XO&8RvwhDJ0U z$6Pw|#l-uQM;k8?MNuK<-qh4oh}hk?&wn)g|5bizWY6F~3(>Q>q4)4qGq<~CsI;?V c?}v8mU!EDKSy}+3$N&HU07*qoM6N<$f>O=Png9R* literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/clock/stop.png b/pyulib/src/uapps/tasks/httpd/static/clock/stop.png new file mode 100644 index 0000000000000000000000000000000000000000..4b145da074d39021afebac4bf6833045a02e5c26 GIT binary patch literal 980 zcmV;_11tQAP)@5)+qgC{&15&3+1X_N|GYDn;Ds{>&N<(A&v)ir z%J+S!cPJXbWn$-unZ(8tFZh8!IHabNxVyv*VlyOP4&s#jEg0h8XENtsjb-0BtXDs) zm9~`1H}P8zi4Q-)7bdkSdMML16lK$tS-SFobc{0(aROI+gmUVDq{d8NBq%{3lv zl%qd(-xdVc1>WCznAK;`3MryEgVyGCbPe~%KFV|Dj4EBH)cPKqCovzP%QRLfQ<$pQ&)!_i-KZ$NT+$2u8%}Kj(^tI zF@NtD>=aknFDLZDSLOynPIgJFRL$6!#dBTA$MpuP(i&@(ZC?8S?)Nv)9Wr9IQU z9uioFI;-qHUM;TgVq4zEja(UzHXCq75w5C&=J`;TZQC$NH?fu$_(Y1QX-|g)zg~Ou z58KeL^(Puyp}LEe^$mP6TYzX#A%V5I3DdG*7zWu3a-k^ta2&XXQ4aSYIQe}3xnweV zueKw*mvetcEGmv;jDst^3N+7wLx^IjR49kyd7qagQ(9OU4~IB6H`h+w?e_L|sQZet zFaQ0K>-lFNkjM0OL*plpcCf<003EaqLb6vVQ1zvy&rjXIpAQKnNz#eiBwb{Qe|Gui zdn^Bc%EhUv5jDSf-8IcYmE+jQqocAmGxP2#lKU5#lCI6)RfGZn0000CW@R@?Gfl0P zw%SV9?z-E!j%}vvwrNglZn&9lyW3vIF&!`ezM~HG&_gr)=6rnL|93cN7#l|K`0DoxwgW|9k+jf`WpvL?h8n%;dd)L9?kum}n&?YZoD3@wq4k<=5lDGd#;3ZIArJ%XCk4 zt0CSx2+5cV^!owz7tj}_n&=Ht?T1u&g7%LC&vHleAD&uewOrnSnnFFo?zh12A5f!S zR9c@Q5^N^F9Z)<7eR_;{Sqb1-?r8q=b8Cb4oLz9_>7dFj=2;^Dk^p6={JUl*Mn-O%ffq9}j)2;naRJ{r$~XSt*KCASkV zxE>va$Ci!eG9$G?-Y>MZexd63ApJ6$%s1h-Wipt z($Hcrg2!T_b2LGY1ke%jBhn(F=~W>-6}d=%dLN4n&aQFU4YlOPush!5Bxf|Zy`nu7 zdspmfsk(|E&inW#7*KD@ME!%Sbf!D_?l8deHWw?4KleTGcJ|JA(k_u3!-{+-x7s7E zrS09=UgZzP8b8PZTPl3j*U?sYdjMWH3zsn0Sf}V+VdYSr{FA&IViYrlIBA=!JHA^2 zDz;Qdd~1sBHuX)Sl?V=DU03{0*z1S}k{UXLl(!R8`27d|L21WuR@$K>oWy%#v^LXL h>P;G&+i8u_`VN^f>j=iajUI&8x?%qT<*M2k`vbLi^63Bo literal 0 HcmV?d00001 diff --git a/pyulib/src/uapps/tasks/httpd/static/index.css b/pyulib/src/uapps/tasks/httpd/static/index.css new file mode 100644 index 0000000..5842a2f --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/index.css @@ -0,0 +1,101 @@ +#stores { + position: absolute; + z-index: 3000; + border: 1px solid #111; + background-color: #eee; + padding: 2px; + opacity: 0.95; +} +#prev_queries { + position: absolute; + z-index: 3000; + border: 1px solid #111; + background-color: #eee; + padding: 2px; + opacity: 0.95; +} + +h3 { + font-size: 1.5em; + line-height: 1.2; + margin-bottom: 0; + background: #303050; + color: white; +} + +.no-margin { + margin: 0; +} + +.line-even { +} +.line-odd { + background-color: #E5E5E5; +} + +.task-div { +} +.task-div label { + font-weight: normal; +} + +.tcheckbox { +} +.tid { +} +.tprojects { + color: #0000A0; +} +.tcontexts { + color: #0000A0; +} +.ttitle { +} +.ticonsbar a { + text-decoration: none; +} +.tidetails { +} +.tiedit { +} +.turgency { +} +.tsummary-div { +} +.tbody-div { +} +.tprefix-div { +} +.tbody { +} +.ttag-div { +} +.ttag { + color: #000050; +} + +.task-type { +} +.event-type { + background-color: #FFFF00; +} +.idea-type { + color: #00A0A0; +} +.activity-type { + background-color: #00FF00; +} + +.urgent { + color: #FF0000; +} +.normal { +} +.planned { +} +.obsolete { + text-decoration: line-through; +} +.done { + text-decoration: line-through; +} diff --git a/pyulib/src/uapps/tasks/httpd/static/index.js b/pyulib/src/uapps/tasks/httpd/static/index.js new file mode 100644 index 0000000..14c3766 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/index.js @@ -0,0 +1,94 @@ +//@require jquery.hoverIntent.js +//@require taskshttpd.js + +// nombre par défaut de minutes au bout desquelles la page est rechargée +var DEFAULT_RELOAD_TIMEOUT= 5; +function reload_page_every(timeout) { + // rechargement de la page toutes les timeout minutes + // La souris et le clavier sont monitorés, et la page est rechargée s'il + // n'y a pas eu de mouvement pendant la durée spécifiée. + if (timeout === undefined) { + timeout = DEFAULT_RELOAD_TIMEOUT; + } + + var mousemoved = false; + var keypressed = false; + $(window).mousemove(function () { + mousemoved = true; + }).keyup(function () { + keypressed = true; + }); + var counter = 0; + window.setInterval(function () { + if (mousemoved || keypressed) { + mousemoved = false; + keypressed = false; + counter = 0; + } + counter++; + if (counter >= timeout) { + if ($(".tcheckbox:enabled:checked").length) { + // Ne pas recharger si une des cases à cocher est cochée + window.location.reload(true); + } else { + counter = 0; + } + } + }, 60 * 1000); +} + +$(function () { + // previous queries + $("#btn_prev_queries").click(function() { + if ($("#prev_queries").toggle().is(":visible")) { + placeUnder("#prev_queries", "#btn_prev_queries"); + } + }); + // select wstore + $("#btn_store").click(function() { + if ($("#stores").toggle().is(":visible")) { + placeUnder("#stores", "#btn_store"); + } + }); + // messages + delayer(2500)(null, function() { + $(".msg_fade").fadeOut("slow"); + }); + + // Barre d'icones + $(".task-div").hoverIntent(function() { + $(this).find(".ticonsbar").show(); + }, function () { + $(this).find(".ticonsbar").hide(); + }); + + // Installers les handlers des icones + // ... tidetails: tooltip + afficher body + var ZOOM_IN = "zoom_in"; + var ZOOM_OUT = "zoom_out"; + $(".tidetails") + .tooltip({ + track: true, + showURL: false, + bodyHandler: function() { + var task = $(this).parents(".task-div"); + return task.find(".tprefix-div").html() + + task.find(".tsummary-div").html() + + task.find(".ttag-div").html(); + }, + }) + .click(function() { + var body = $(this).parents(".task-div").find(".tbody-div"); + body.toggle(); + var hidden = body.is(":hidden"); + $(this).toggleClass(ZOOM_IN, hidden).toggleClass(ZOOM_OUT, !hidden); + return false; + }); + + // double-click pour éditer + $(".task-div").dblclick(function() { + var form = $("#actionform"); + form.find("#query").attr("value", "/e " + $(this).attr("id")); + form.submit(); + }); +}); diff --git a/pyulib/src/uapps/tasks/httpd/static/index.min.js b/pyulib/src/uapps/tasks/httpd/static/index.min.js new file mode 100644 index 0000000..f0dc4ce --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/index.min.js @@ -0,0 +1 @@ +var DEFAULT_RELOAD_TIMEOUT=5;function reload_page_every(d){if(d===undefined){d=DEFAULT_RELOAD_TIMEOUT}var b=false;var c=false;$(window).mousemove(function(){b=true}).keyup(function(){c=true});var a=0;window.setInterval(function(){if(b||c){b=false;c=false;a=0}a++;if(a>=d){if($(".tcheckbox:enabled:checked").length){window.location.reload(true)}else{a=0}}},60*1000)}$(function(){$("#btn_prev_queries").click(function(){if($("#prev_queries").toggle().is(":visible")){placeUnder("#prev_queries","#btn_prev_queries")}});$("#btn_store").click(function(){if($("#stores").toggle().is(":visible")){placeUnder("#stores","#btn_store")}});delayer(2500)(null,function(){$(".msg_fade").fadeOut("slow")});$(".task-div").hoverIntent(function(){$(this).find(".ticonsbar").show()},function(){$(this).find(".ticonsbar").hide()});var b="zoom_in";var a="zoom_out";$(".tidetails").tooltip({track:true,showURL:false,bodyHandler:function(){var c=$(this).parents(".task-div");return c.find(".tprefix-div").html()+c.find(".tsummary-div").html()+c.find(".ttag-div").html()},}).click(function(){var c=$(this).parents(".task-div").find(".tbody-div");c.toggle();var d=c.is(":hidden");$(this).toggleClass(b,d).toggleClass(a,!d);return false});$(".task-div").dblclick(function(){var c=$("#actionform");c.find("#query").attr("value","/e "+$(this).attr("id"));c.submit()})}); \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/jquery.hoverIntent.js b/pyulib/src/uapps/tasks/httpd/static/jquery.hoverIntent.js new file mode 100644 index 0000000..bd11442 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/jquery.hoverIntent.js @@ -0,0 +1,111 @@ +/** +* hoverIntent is similar to jQuery's built-in "hover" function except that +* instead of firing the onMouseOver event immediately, hoverIntent checks +* to see if the user's mouse has slowed down (beneath the sensitivity +* threshold) before firing the onMouseOver event. +* +* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+ +* +* +* hoverIntent is currently available for use in all personal or commercial +* projects under both MIT and GPL licenses. This means that you can choose +* the license that best suits your project, and use it accordingly. +* +* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions +* $("ul li").hoverIntent( showNav , hideNav ); +* +* // advanced usage receives configuration object only +* $("ul li").hoverIntent({ +* sensitivity: 7, // number = sensitivity threshold (must be 1 or higher) +* interval: 100, // number = milliseconds of polling interval +* over: showNav, // function = onMouseOver callback (required) +* timeout: 0, // number = milliseconds delay before onMouseOut function call +* out: hideNav // function = onMouseOut callback (required) +* }); +* +* @param f onMouseOver function || An object with configuration options +* @param g onMouseOut function || Nothing (use configuration options object) +* @author Brian Cherne +*/ +(function($) { + $.fn.hoverIntent = function(f,g) { + // default configuration options + var cfg = { + sensitivity: 7, + interval: 100, + timeout: 0 + }; + // override configuration options with user supplied object + cfg = $.extend(cfg, g ? { over: f, out: g } : f ); + + // instantiate variables + // cX, cY = current X and Y position of mouse, updated by mousemove event + // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval + var cX, cY, pX, pY; + + // A private function for getting mouse position + var track = function(ev) { + cX = ev.pageX; + cY = ev.pageY; + }; + + // A private function for comparing current and previous mouse position + var compare = function(ev,ob) { + ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); + // compare mouse positions to see if they've crossed the threshold + if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) { + $(ob).unbind("mousemove",track); + // set hoverIntent state to true (so mouseOut can be called) + ob.hoverIntent_s = 1; + return cfg.over.apply(ob,[ev]); + } else { + // set previous coordinates for next time + pX = cX; pY = cY; + // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs) + ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval ); + } + }; + + // A private function for delaying the mouseOut function + var delay = function(ev,ob) { + ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); + ob.hoverIntent_s = 0; + return cfg.out.apply(ob,[ev]); + }; + + // A private function for handling mouse 'hovering' + var handleHover = function(e) { + // next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut + var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget; + while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } } + if ( p == this ) { return false; } + + // copy objects to be passed into t (required for event object to be passed in IE) + var ev = jQuery.extend({},e); + var ob = this; + + // cancel hoverIntent timer if it exists + if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); } + + // else e.type == "onmouseover" + if (e.type == "mouseover") { + // set "previous" X and Y position based on initial entry point + pX = ev.pageX; pY = ev.pageY; + // update "current" X and Y position based on mousemove + $(ob).bind("mousemove",track); + // start polling interval (self-calling timeout) to compare mouse coordinates over time + if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );} + + // else e.type == "onmouseout" + } else { + // unbind expensive mousemove event + $(ob).unbind("mousemove",track); + // if hoverIntent state is true, then call the mouseOut function after the specified delay + if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );} + } + }; + + // bind the function to the two event listeners + return this.mouseover(handleHover).mouseout(handleHover); + }; +})(jQuery); \ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpd/static/jquery.hoverIntent.min.js b/pyulib/src/uapps/tasks/httpd/static/jquery.hoverIntent.min.js new file mode 100644 index 0000000..5f2bbcd --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/static/jquery.hoverIntent.min.js @@ -0,0 +1 @@ +(function(a){a.fn.hoverIntent=function(k,j){var l={sensitivity:7,interval:100,timeout:0};l=a.extend(l,j?{over:k,out:j}:k);var n,m,h,d;var e=function(f){n=f.pageX;m=f.pageY};var c=function(g,f){f.hoverIntent_t=clearTimeout(f.hoverIntent_t);if((Math.abs(h-n)+Math.abs(d-m)))[^>]*$|^#([\w-]+)$/, + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + this.context = selector; + return this; + } + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem && elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + var ret = jQuery( elem || [] ); + ret.context = document; + ret.selector = selector; + return ret; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return jQuery( document ).ready( selector ); + + // Make sure that old selector state is passed along + if ( selector.selector && selector.context ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return this.setArray(jQuery.isArray( selector ) ? + selector : + jQuery.makeArray(selector)); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.3.2", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num === undefined ? + + // Return a 'clean' array + Array.prototype.slice.call( this ) : + + // Return just the object + this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) + ret.selector = this.selector + (this.selector ? " " : "") + selector; + else if ( name ) + ret.selector = this.selector + "." + name + "(" + selector + ")"; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem && elem.jquery ? elem[0] : elem + , this ); + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( typeof name === "string" ) + if ( value === undefined ) + return this[0] && jQuery[ type || "attr" ]( this[0], name ); + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text !== "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).clone(); + + if ( this[0].parentNode ) + wrap.insertBefore( this[0] ); + + wrap.map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: [].push, + sort: [].sort, + splice: [].splice, + + find: function( selector ) { + if ( this.length === 1 ) { + var ret = this.pushStack( [], "find", selector ); + ret.length = 0; + jQuery.find( selector, this[0], ret ); + return ret; + } else { + return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + })), "find", selector ); + } + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var html = this.outerHTML; + if ( !html ) { + var div = this.ownerDocument.createElement("div"); + div.appendChild( this.cloneNode(true) ); + html = div.innerHTML; + } + + return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; + } else + return this.cloneNode(true); + }); + + // Copy the events from the original to the clone + if ( events === true ) { + var orig = this.find("*").andSelf(), i = 0; + + ret.find("*").andSelf().each(function(){ + if ( this.nodeName !== orig[i].nodeName ) + return; + + var events = jQuery.data( orig[i], "events" ); + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + + i++; + }); + } + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ + return elem.nodeType === 1; + }) ), "filter", selector ); + }, + + closest: function( selector ) { + var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, + closer = 0; + + return this.map(function(){ + var cur = this; + while ( cur && cur.ownerDocument ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { + jQuery.data(cur, "closest", closer); + return cur; + } + cur = cur.parentNode; + closer++; + } + }); + }, + + not: function( selector ) { + if ( typeof selector === "string" ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return this.pushStack( jQuery.unique( jQuery.merge( + this.get(), + typeof selector === "string" ? + jQuery( selector ) : + jQuery.makeArray( selector ) + ))); + }, + + is: function( selector ) { + return !!selector && jQuery.multiFilter( selector, this ).length > 0; + }, + + hasClass: function( selector ) { + return !!selector && this.is( "." + selector ); + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if( jQuery.nodeName( elem, 'option' ) ) + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Everything else, we just grab the value + return (elem.value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + if ( typeof value === "number" ) + value += ''; + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(value); + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value === undefined ? + (this[0] ? + this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, +i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ), + "slice", Array.prototype.slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + domManip: function( args, table, callback ) { + if ( this[0] ) { + var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), + scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), + first = fragment.firstChild; + + if ( first ) + for ( var i = 0, l = this.length; i < l; i++ ) + callback.call( root(this[i], first), this.length > 1 || i > 0 ? + fragment.cloneNode(true) : fragment ); + + if ( scripts ) + jQuery.each( scripts, evalScript ); + } + + return this; + + function root( elem, cur ) { + return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; + } + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +function now(){ + return +new Date; +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + var src = target[ name ], copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) + continue; + + // Recurse if we're merging object values + if ( deep && copy && typeof copy === "object" && !copy.nodeType ) + target[ name ] = jQuery.extend( deep, + // Never move original objects, clone them + src || ( copy.length != null ? [ ] : { } ) + , copy ); + + // Don't bring in undefined values + else if ( copy !== undefined ) + target[ name ] = copy; + + } + + // Return the modified object + return target; +}; + +// exclude the following css properties to add px +var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, + // cache defaultView + defaultView = document.defaultView || {}, + toString = Object.prototype.toString; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && /\S/.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.support.scriptEval ) + script.appendChild( document.createTextNode( data ) ); + else + script.text = data; + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, length = object.length; + + if ( args ) { + if ( length === undefined ) { + for ( name in object ) + if ( callback.apply( object[ name ], args ) === false ) + break; + } else + for ( ; i < length; ) + if ( callback.apply( object[ i++ ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( length === undefined ) { + for ( name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames !== undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use hasClass("class") + has: function( elem, className ) { + return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force, extra ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) + return; + + jQuery.each( which, function() { + if ( !extra ) + val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + if ( extra === "margin" ) + val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; + else + val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + } + + if ( elem.offsetWidth !== 0 ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, Math.round(val)); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret, style = elem.style; + + // We need to handle opacity special in IE + if ( name == "opacity" && !jQuery.support.opacity ) { + ret = jQuery.attr( style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && style && style[ name ] ) + ret = style[ name ]; + + else if ( defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var computedStyle = defaultView.getComputedStyle( elem, null ); + + if ( computedStyle ) + ret = computedStyle.getPropertyValue( name ); + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = ret || 0; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + + return ret; + }, + + clean: function( elems, context, fragment ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { + var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); + if ( match ) + return [ context.createElement( match[1] ) ]; + } + + var ret = [], scripts = [], div = context.createElement("div"); + + jQuery.each(elems, function(i, elem){ + if ( typeof elem === "number" ) + elem += ''; + + if ( !elem ) + return; + + // Convert html string into DOM nodes + if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? + all : + front + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); + + var wrap = + // option or optgroup + !tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "", "
" ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and +Edition d'une tâche + + +
+
+ + +
+ + $for name, value in t.ep_items: + + + + + + + +
$name + $ ep_class = t.ep_class(name) + $ ep_style = t.ep_style(name) + $if ep_class == "textarea": + + $else: + +
+
+
+
+
+ + diff --git a/pyulib/src/uapps/tasks/httpd/templates/index.html b/pyulib/src/uapps/tasks/httpd/templates/index.html new file mode 100644 index 0000000..12ffbfb --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/templates/index.html @@ -0,0 +1,85 @@ +$def with (self) + + + + + + +$:blueprintcss("/static/blueprint") +$:js("/static/jquery.min.js") +$:jscss("/static/jquery.tooltip.min.js") +$:js("/static/jquery.hoverIntent.min.js") +$:css("/static/zoom.css") +$:css("/static/clock.css") +$:js("/static/taskshttpd.min.js") +$:jscss("/static/index.min.js") + +Serveur de tâches + + +
+
+
+ + $if self.app.cfilters: +  ?  + + + + $ wname = self.app.ctl.get_wstore().get_name() + [$wname] +

Choisissez le store destination

+
    + $for store in self.app.ctl.get_stores().get_stores(): + $ name = store.get_name() + $ sid = store.get_sid() + $ current = name == wname and ' *' or '' +
  • $name ($sid)$current
  • +
+ $if self.app.cfilters: +

Choisissez une requête déjà effectuée

+
    + $for cfilter in self.app.cfilters: + $if cfilter:
  • $cfilter
  • +
+
+ $if self.msgs: +
+ $for msg in self.msgs:
$msg.msg_value
+
+
+
+ $for t in [t for t in self.ts if t.cal_today and (t.cal_planned or t.done)]: + $if loop.index == 1:

Aujourd'hui

+
$:self.hts[t.id]
+ $for t in [t for t in self.ts if t.cal_today and not t.cal_planned and not t.done]: + $if loop.index == 1:

Non planifié

+
$:self.hts[t.id]
+
+
+
+
+ $for t in [t for t in self.ts if t.is_cal_future(1)]: + $if loop.index == 1:

Demain

+
$:self.hts[t.id]
+ $for t in [t for t in self.ts if t.is_cal_future(2)]: + $if loop.index == 1:

Après-demain

+
$:self.hts[t.id]
+ $for t in [t for t in self.ts if t.is_cal_future(3, True)]: + $if loop.index == 1:

Plus tard

+
$:self.hts[t.id]
+ $for t in [t for t in self.ts if t.is_cal_future(-1)]: + $if loop.index == 1:

Effectuées

+
$:self.hts[t.id]
+
+
+
+
+ + diff --git a/pyulib/src/uapps/tasks/httpd/templates/task.htmlc b/pyulib/src/uapps/tasks/httpd/templates/task.htmlc new file mode 100644 index 0000000..61fd8ad --- /dev/null +++ b/pyulib/src/uapps/tasks/httpd/templates/task.htmlc @@ -0,0 +1,39 @@ +$def with (t) +
+
+\ +$elif t.done: disabled="disabled" checked="checked" />\ +$else: disabled="disabled" />\ +\ +$if t.started: | Started + | \ +Details | \ +Edit\ + +
+
+
$t.get_htbody(128)
+
+
+
+
+$if t.urgency: ($t.urgency) +
+
+
$t.htbody
+
+$for name, value in t.get_props().items():
$name=$value
+
+
+
\ No newline at end of file diff --git a/pyulib/src/uapps/tasks/httpstore/__init__.py b/pyulib/src/uapps/tasks/httpstore/__init__.py new file mode 100644 index 0000000..590f57e --- /dev/null +++ b/pyulib/src/uapps/tasks/httpstore/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = () diff --git a/pyulib/src/uapps/tasks/httpstore/server.py b/pyulib/src/uapps/tasks/httpstore/server.py new file mode 100644 index 0000000..d848e46 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpstore/server.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = ('Server',) + +try: True, False +except: True, False = 1, 0 + +from ulib.web import web, Application, Page, defaults +from ulib.tasks import factories, TasksCtl + +class Server(Application): + PORT = 1975 + + _ctl = None + _stores = None + def get_stores(self): + stores = self._stores + if stores is not None: return stores + else: return self._ctl.get_stores() + stores = property(get_stores) + + def __init__(self, basedir=None, templatedir=None, host=None, port=None, debug=None, + tsrc=None, ctl=None): + Application.__init__(self, basedir, templatedir, host, port, debug) + + if ctl is None: ctl = TasksCtl(tsrc) + self._ctl = ctl + self.OPTIONS = self._ctl.OPTIONS + self.LONG_OPTIONS = self._ctl.LONG_OPTIONS + + def is_option(self, option, value): + return self._ctl.is_option(option, value) + + def process_args(self, args): + if args: self._stores = factories.get_store(args) + +class index(Page): + PREFIX = r'/' + + def index(self): + return self.render(self.app.stores.get_stores()) + + def utf8plaintext(self, text): + web.header("Content-Type", "text/plain; charset=utf-8", True) + return text.encode("utf-8") + + def list(self): + lines = [] + for store in self.app.stores.get_stores(): + lines.append(u"%s:%s" % (store.sid, store.name)) + return self.utf8plaintext(u"\n".join(lines)) + + @defaults(sid=None) + def head(self): + store = self.app.stores.get_store_by_sid(self.sid) + if store is None: return self.error() + else: return self.utf8plaintext(u"\n".join(store.get_hlines())) + + @defaults(sid=None) + def get(self): + store = self.app.stores.get_store_by_sid(self.sid) + if store is None: return self.error() + else: return self.utf8plaintext(u"\n".join(store.get_lines())) + +if __name__ == '__main__': + import sys + from os import path + + basedir = path.abspath(path.split(__file__)[0]) + Server(basedir).run(sys.argv[1:]) diff --git a/pyulib/src/uapps/tasks/httpstore/templates/index.html b/pyulib/src/uapps/tasks/httpstore/templates/index.html new file mode 100644 index 0000000..8587cc4 --- /dev/null +++ b/pyulib/src/uapps/tasks/httpstore/templates/index.html @@ -0,0 +1,26 @@ +$def with (stores) + + + + + +httpstore + + +

httpstore

+

Liste des stores gérés par ce serveur:

+ + + + + +$for store in stores: + $ sid = _u(store.sid) + + + + +
sidname
$sid$store.name
+ + diff --git a/pyulib/src/uapps/tasks/itfctl.py b/pyulib/src/uapps/tasks/itfctl.py new file mode 100644 index 0000000..b13905c --- /dev/null +++ b/pyulib/src/uapps/tasks/itfctl.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = ('get_tasks_itf',) + +from ulib.base.uio import _u, _s +from ulib.tasks import TasksCtl +from taskscli import TasksCLI + +def get_cmd_name(args, required=False): + cmd_name = args[0:1] and args[0] or None + if required and cmd_name is None: + raise ValueError("You must specify a command") + return cmd_name, args[1:] + +def get_httpd(): + import httpd.server + return httpd.server.Server + +def get_httpstore(): + import httpstore.server + return httpstore.server.Server + +DEFAULT_CLASS_GETTER = lambda: TasksCLI +CLASSES_GETTERS = {'httpd': get_httpd, + 'httpstore': get_httpstore, + } +def get_tasks_cls(args): + cmd_name, args = get_cmd_name(args) + if cmd_name is None: cls_getter = None + else: cls_getter = CLASSES_GETTERS.get(cmd_name, DEFAULT_CLASS_GETTER) + if cls_getter is None: cls = None + else: cls = cls_getter() + return cls, cmd_name, args + +def get_tasks_itf(args, ctl=None): + cls, cmd_name, args = get_tasks_cls(args) + if cls is None: itf = None + else: itf = cls(ctl=ctl) + return itf, cmd_name, args diff --git a/pyulib/src/uapps/tasks/taskscli.py b/pyulib/src/uapps/tasks/taskscli.py new file mode 100644 index 0000000..6f9d125 --- /dev/null +++ b/pyulib/src/uapps/tasks/taskscli.py @@ -0,0 +1,422 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = ('TasksCLI',) + +from ulib.base.output import uprint, enote, eerror, ewarn, get_color +from ulib.base.words import plural +from ulib.base.uio import _u +from ulib.base.control import die, ERROR_STATUS +from ulib.base.editor import edit_template +from ulib.base.lines import Lines +from ulib.base.dates import parse_date, Date +from ulib.tasks.tasks import Task +from ulib.tasks.tasksctl import TasksCtl + +class TasksCLI(TasksCtl): + def __init__(self, tsrc=None, ctl=None): + TasksCtl.__init__(self, tsrc, ctl) + + def get_width(self, tasks): + return max(map(lambda t: len(t.id), tasks) or [1]) + + # ========================================================================= + + def __print_task(self, t, width=1, showbody=False): + parts = [] + + prefix_color = u"" + reset_color = get_color("reset") + if t.is_task_type(): + if t.is_urgent(): + prefix = u"[ ]" + prefix_color = get_color(self._tsrc.get_urgent_color()) + elif t.is_normal(): + prefix = u"[ ]" + prefix_color = get_color(self._tsrc.get_normal_color()) + elif t.is_planned(): + prefix = u"Pl>" + prefix_color = get_color(self._tsrc.get_planned_color()) + elif t.is_obsolete(): + prefix = u"Ob>" + prefix_color = get_color(self._tsrc.get_obsolete_color()) + elif t.is_done(): + prefix = u"[x]" + prefix_color = get_color(self._tsrc.get_done_color()) + else: + prefix = u"??>" + elif t.is_event_type(): + if t.is_done(): + prefix = u"[x]" + prefix_color = get_color(self._tsrc.get_done_color()) + else: + prefix = u"Ev>" + prefix_color = get_color(self._tsrc.get_event_color()) + elif t.is_idea_type(): + prefix = u"Id>" + prefix_color = get_color(self._tsrc.get_idea_color()) + elif t.is_activity_type(): + prefix = u"Ac>" + prefix_color = get_color(self._tsrc.get_activity_color()) + else: + prefix = u"??>" + parts.append(prefix_color) + parts.append(prefix) + parts.append(u" %*s " % (width, t.id)) + project_color = get_color(self._tsrc.get_project_color()) + for p in t.get_projects(): + parts.append(project_color) + parts.append(p) + parts.append(reset_color) + parts.append(prefix_color) + parts.append(u" ") + context_color = get_color(self._tsrc.get_context_color()) + for c in t.get_contexts(): + parts.append(context_color) + parts.append(c) + parts.append(reset_color) + parts.append(prefix_color) + parts.append(u" ") + parts.append(t.get_title1()) + if t.is_task_type() or t.is_event_type(): + urgency = t.get_urgency() + if urgency: + parts.append(" (") + parts.append(urgency) + parts.append(")") + parts.append(reset_color) + uprint(''.join(parts)) + + body = t.get_body() + if showbody: + if body: uprint(body) + tag_color = get_color(self._tsrc.get_tag_color()) + for name, value in t.get_props().items(): + uprint(u"%s%s=%s%s" % (tag_color, name, value, reset_color)) + + def __print_stats(self, tasks, nb_showable, nb_total): + tasks_of_task_type = [task for task in tasks if task.is_task_type()] + nb_tasks = len(tasks_of_task_type) + nb_done = len([task for task in tasks_of_task_type if task.is_done()]) + infos = [] + if nb_total != nb_tasks: + infos.append(plural(u"%i ligne%# au total", nb_total)) + if nb_total != nb_showable: + infos.append(plural(u"%i ligne%# masquée%#", nb_total - nb_showable)) + if infos: notes = u" [%s]" % u", ".join(infos) + else: notes = u"" + enote(u"%i / %s%s" % (nb_tasks - nb_done, plural(u"%i tâche%#", nb_tasks), notes)) + + SHOW_ALL = 0 + SHOW_SHOWABLE = 1 + SHOW_DOABLE = 2 + + def list(self, ids=None, text=None, props=None, projects=None, contexts=None, + showwhat=False, showbody=False, showstats=True, store=None): + if store is None: store = self.get_stores() + tasks = store.find_tasks(ids, text, props, projects, contexts) + nb_total = len(tasks) + if showwhat == self.SHOW_ALL: + pass + elif showwhat == self.SHOW_SHOWABLE: + tasks = [task for task in tasks if task.is_showable()] + elif showwhat == self.SHOW_DOABLE: + tasks = [task for task in tasks if task.is_doable()] + tasks.sort(Task.cmp) + nb_showable = len(tasks) + width = self.get_width(tasks) + for t in tasks: + self.__print_task(t, width, showbody=showbody) + if showstats: + self.__print_stats(tasks, nb_showable, nb_total) + + def list_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args, 'aqdln', + ['show-all', 'show-showable', 'show-doable', + 'show-body', 'no-stats']) + showwhat = None + showbody = False + showstats = True + for option, value in options: + if option in ('-a', '--show-all'): showwhat = self.SHOW_ALL + elif option in ('-q', '--show-showable'): showwhat = self.SHOW_SHOWABLE + elif option in ('-d', '--show-doable'): showwhat = self.SHOW_DOABLE + elif option in ('-l', '--show-body'): showbody = True + elif option in ('-n', '--no-stats'): showstats = False + elif self.is_option(option, value): pass + + store = self.get_stores() + ids, text, props, projects, contexts, filtered = self.get_filters(map(_u, args), store=store) + if showwhat is None: + if filtered: showwhat = self.SHOW_ALL + else: showwhat = self.SHOW_SHOWABLE + self.list(ids, text, props, projects, contexts, showwhat, showbody, showstats, store=store) + + # ========================================================================= + + def add(self, args, do=False, edit=False): + store = self.get_wstore() + t = store.new_task_from_clargs(args) + if do: t.do() + t.save() + self.__print_task(t) + if edit: self.edit(t) + + def add_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args, 'de', ['do', 'edit']) + do = False + edit = False + for option, value in options: + if option in ('-d', '--do'): do = True + elif option in ('-e', '--edit'): edit = True + elif self.is_option(option, value): pass + + return self.add(args, do, edit) + + # ========================================================================= + + def edit(self, task=None, tasks=None): + if tasks is None and task is not None: + tasks = [task] + if tasks is None: return + + width = self.get_width(tasks) + for t in tasks: + title = Lines(t.title) + title = title[0:1] and title[0] or u"" + text = edit_template(t.output(join=True, help=True, modeline=True, lastnl=True), row=3, col=len(title)) + t.input(text) + t.save() + self.__print_task(t, width, showbody=True) + + def edit_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args) + for option, value in options: + if self.is_option(option, value): pass + + store = self.get_stores() + tasks = self.get_filtered(args, store=store) + self.edit(tasks=tasks) + + # ========================================================================= + + def do(self, task=None, tasks=None): + if tasks is None and task is not None: + tasks = [task] + if tasks is None: return + + width = self.get_width(tasks) + for t in tasks: + t.do() + t.save() + self.__print_task(t, width, showbody=True) + + def do_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args) + for option, value in options: + if self.is_option(option, value): pass + + store = self.get_stores() + tasks = self.get_filtered(args, store=store) + self.do(tasks=tasks) + + def nodo(self, task=None, tasks=None): + if tasks is None and task is not None: + tasks = [task] + if tasks is None: return + + width = self.get_width(tasks) + for t in tasks: + t.nodo() + t.save() + self.__print_task(t, width, showbody=True) + + def nodo_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args) + for option, value in options: + if self.is_option(option, value): pass + + store = self.get_stores() + tasks = self.get_filtered(args, store=store) + self.nodo(tasks=tasks) + + # ========================================================================= + + def __one(self, tasks): + if len(tasks) == 1: return tasks[0] + elif len(tasks) > 1: eerror(u"Plusieurs tâches correspondent au filtre") + else: eerror(u"Aucune tâche trouvée") + return None + + def start(self, task, summary=None, exclusive=False, store=None): + u""" + """ + if exclusive: + if store is None: store = task.get_store() + store.stop_all() + store.save() + date, time = task.start(summary) + task.save() + self.__print_task(task) + enote(u"Démarrage de la tâche à %s le %s" % (time, date)) + + def start_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args, 'x', ['exclusive']) + exclusive = False + for option, value in options: + if option in ('-x', '--exclusive'): exclusive = True + elif self.is_option(option, value): pass + + store = self.get_stores() + task = self.__one(self.get_filtered(args[0:1], store=store)) + summary = u" ".join(args[1:]) + if task is not None: + self.start(task, summary=summary or None, + exclusive=exclusive, store=store) + + def stop(self, task, summary=None): + u""" + """ + date, time = task.stop(summary) + task.save() + self.__print_task(task) + enote(u"Arrêt de la tâche à %s le %s" % (time, date)) + + def stop_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args, 'a', ['stop-all']) + stop_all = False + for option, value in options: + if option in ('-a', '--stop-all'): stop_all = True + elif self.is_option(option, value): pass + + store = self.get_stores() + if stop_all: + if store.stop_all(): + enote(u"Toutes les tâches ont été arrêtées") + else: + task = self.__one(self.get_filtered(args[0:1], store=store)) + summary = u" ".join(args[1:]) + if task is not None: self.stop(task, summary=summary or None) + + def toggle_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args, 'x', ['exclusive']) + exclusive = False + for option, value in options: + if option in ('-x', '--exclusive'): exclusive = True + elif self.is_option(option, value): pass + + store = self.get_stores() + task = self.__one(self.get_filtered(args[0:1], store=store)) + summary = u" ".join(args[1:]) + if task is not None: + if task.is_started(): + self.stop(task, summary=summary or None) + else: + self.start(task, summary=summary or None, + exclusive=exclusive, store=store) + + # ========================================================================= + + def plan(self, date=None, task=None, tasks=None): + if tasks is None and task is not None: + tasks = [task] + if tasks is None: return + + width = self.get_width(tasks) + for t in tasks: + t.plan(date) + t.save() + self.__print_task(t, width, showbody=True) + + def plan_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args, 'd:', ['date=']) + date = Date() + for option, value in options: + if option in ('-d', '--date'): + if value: date = parse_date(value) + else: date = None + elif self.is_option(option, value): pass + + store = self.get_stores() + tasks = self.get_filtered(args, store=store) + self.plan(date, tasks=tasks) + + # ========================================================================= + + def remove(self, task=None, tasks=None): + if tasks is None and task is not None: + tasks = [task] + if tasks is None: return + + width = self.get_width(tasks) + for t in tasks: + self.__print_task(t, width, showbody=True) + store = t.get_store() + if store is None: continue + + if store.remove(t): + store.save() + enote(u"Tâche supprimée") + else: + ewarn(u"Tâche non supprimée") + + def remove_cmd(self, *args, **kw): + u""" + """ + options, args = self.get_args(args) + for option, value in options: + if self.is_option(option, value): pass + + store = self.get_stores() + tasks = self.get_filtered(args, store=store) + self.remove(tasks=tasks) + + # ========================================================================= + + def purge_cmd(self, *args, **kw): + u""" + """ + store = self.get_stores() + + # ========================================================================= + + def report_cmd(self, *args, **kw): + u""" + """ + store = self.get_stores() + + # ========================================================================= + + def run(self, args=None, cmd_name=None, **ignored): + cmd = {'add': self.add_cmd, + 'list': self.list_cmd, + 'edit': self.edit_cmd, + 'do': self.do_cmd, + 'nodo': self.nodo_cmd, + 'start': self.start_cmd, + 'stop': self.stop_cmd, + 'toggle': self.toggle_cmd, + 'plan': self.plan_cmd, + 'remove': self.remove_cmd, + 'purge': self.purge_cmd, + 'report': self.report_cmd, + }.get(cmd_name, None) + if cmd is None: die(u"Commande invalide: %s" % _u(cmd_name)) + return cmd(*args) diff --git a/pyulib/src/uapps/uencdetect.py b/pyulib/src/uapps/uencdetect.py new file mode 100755 index 0000000..3e06de4 --- /dev/null +++ b/pyulib/src/uapps/uencdetect.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +u"""%(name)s - Détecter l'encoding d'un fichier ou d'une chaine +USAGE + %(name)s "string" + %(name)s -f inputfile""" + +import sys + +from ulib.all import * + +def run_uencdetect(): + def print_help(): + uprint(__doc__ % {'name': scriptname}) + + ins = None + infile = None + inf = None + close_inf = False + + options, longoptions = build_options([ + (None, 'help', u"Afficher l'aide"), + ('f:', 'input-file=', u"Fichier dont il faut détecter l'encoding"), + ]) + options, args = get_args(None, options, longoptions) + for option, value in options: + if option in ('--help', ): + print_help() + sys.exit(0) + elif option in ('-f', '--input-file'): + if value == '-': inf = sys.stdin + else: infile = value + + if infile is not None: + inf = open(infile, 'rb') + close_inf = True + elif args: ins = ' '.join(args) + else: inf = sys.stdin + + try: print guess_encoding(ins, inf) + finally: + if close_inf: inf.close() + +if __name__ == '__main__': + run_uencdetect() diff --git a/pyulib/src/uapps/uproject.py b/pyulib/src/uapps/uproject.py new file mode 100755 index 0000000..75fcc6f --- /dev/null +++ b/pyulib/src/uapps/uproject.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +import os, sys, re +from os import path + +from ulib.all import * +from ulib.p.templ import * + +def new_cmd(*args): + """ + @return: le status d'exécution de la commande. + @rtype: Status + """ + class NewOptions(Options): + def __init__(self): + super(NewOptions, self).__init__() + self.add_option('+') + self.add_option('--help', Options.SetValue(True)) + self.add_option(('--template=', '-t:'), Options.SetValue()) + self.add_option(('--edit', '-e'), Options.SetValue(True, initial=True)) + self.add_option(('--noedit', '-n'), Options.SetValue(False, 'edit')) + # Options standards de Templ et FileTempl + self.add_option('--nl=', Options.SetValue()) + self.add_option(('--overwrite', '-f'), Options.SetValue(True)) + self.add_option(('--encoding=', '--enc=', '-E:'), Options.SetValue()) + self.add_option(('--executable', '--exec', '-x'), Options.SetValue(True)) + self.add_option(('--noexec', '-n'), Options.SetValue(False, 'executable')) + + options = NewOptions() + optvalues, args = options.parse(args) + + if options.help: + print "XXX help" + return OK_STATUS + + if not args: + eerror(u"Vous devez spécifier le fichier à créer") + return ERROR_STATUS + + file, args = args[0], args[1:] + templ = get_templ(file, options.template) + estep(u"Sélection du modèle %s" % seqof(templ.NAME)[0]) + + status = OK_STATUS + try: + # parser la ligne de commande + templ.parse_args(args) + # ajouter les options spécifiées ici + for name in ('nl', 'overwrite', 'encoding', 'executable'): + if options.was_parsed(name): + value = options.get_value(name) + templ.set_property(name, value) + # puis créer le fichier + templ.create() + except CannotOverwriteError: + eerror(u"Refus d'écraser le fichier cible (utiliser -f)") + status = ERROR_STATUS + except IOError: + eerror() + status = ERROR_STATUS + + if status == OK_STATUS and options.edit and templ.editable is not None: + file = templ.editable + etitle(u"Edition de %s" % _u(file)) + if ask_yesno(u"Voulez-vous éditer %s?" % _u(file), True, I_INTER): + status = edit_file(file) + + return status + +def run_uproject(): + args = sys.argv[1:] + + cmd_name = args[0:1] and args[0] or None + if cmd_name is None: die("La commande est requise") + args = args[1:] + + cmd = {'new': new_cmd, + }.get(cmd_name, None) + if cmd is None: die(u"Commande invalide: %s" % cmd_name) + + try: + exit(cmd(*args)) + except Exit: raise + except: die() + +if __name__ == '__main__': + run_uproject() diff --git a/pyulib/src/uapps/ur_license.txt b/pyulib/src/uapps/ur_license.txt new file mode 100644 index 0000000..e96178f --- /dev/null +++ b/pyulib/src/uapps/ur_license.txt @@ -0,0 +1,28 @@ +LICENCE d'utilisation de @@projname@@ + + Copyright (c) @@year@@, Universite de la Reunion + All rights reserved + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/pyulib/src/uapps/urandomize.py b/pyulib/src/uapps/urandomize.py new file mode 100755 index 0000000..8c2370b --- /dev/null +++ b/pyulib/src/uapps/urandomize.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +import os, sys, string +from os import path +from sys import stdin, stdout, exit, argv +from string import join, strip, atoi +try: from random import choice +except: from whrandom import choice + +from ulib.all import uprint + +def quote_maybe(str, quote=True): + if quote: str = "'%s'" % str.replace("'", "'\\''") + return str + +def run_urandomize(): + global argv + + if len(argv) == 2 and argv[1] in ('--help', ): + progname = path.split(argv[0])[1] + uprint(u"""%(progname)s -- Mélanger des lignes de façon aléatoire + + USAGE + %(progname)s [-j | -J size] + + OPTIONS + -j, -J size + Fusionner les lignes pour en faire des lignes de taille maximum $size + octets (920 par défaut. Utiliser -1 pour pas de limite.) + -q Mettre les valeurs entre quotes + """ % vars()) + exit(0) + + DEFAULT_SIZE = 920 + size = None + quote = False + + argv = argv[1:] + while argv: + if argv[0][:1] == '-': + opt = argv[0][:2] + if opt == '-j': + size = DEFAULT_SIZE + + elif opt == '-J': + if not argv[0][2:]: + if not argv[1:2]: + raise ValueError, "-J attend un argument" + else: + arg = argv[1] + argv = argv[1:] + else: + arg = argv[0][2:] + size = atoi(arg) + + elif opt == '-q': + quote = True + + elif opt == '--': + break + + else: + raise ValueError, "Option invalide" + else: + break + argv = argv[1:] + + if argv: + # 'lignes' données en ligne de commande + input = argv + else: + input = [] + while 1: + line = stdin.readline() + if not line: break + while line[ - 1:] in ('\n', '\r'): + line = line[: - 1] + input.append(line) + + if size: + # Il faut fusionner les lignes + # On en profite pour les striper + output = [] + line = '' + while input: + item = choice(input) + input.remove(item) + + if line: line = line + ' ' + line = line + strip(quote_maybe(item, quote)) + + if size != - 1 and len(line) >= size: + output.append(line) + line = '' + if line: + output.append(line) + + else: + # On se contente de randomiser les lignes + output = [] + while input: + item = choice(input) + input.remove(item) + output.append(quote_maybe(item, quote)) + + for item in output: + stdout.write(item + '\n') + +if __name__ == '__main__': + run_urandomize() diff --git a/pyulib/src/ulib/__init__.py b/pyulib/src/ulib/__init__.py new file mode 100644 index 0000000..bec7e8f --- /dev/null +++ b/pyulib/src/ulib/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +__all__ = () diff --git a/pyulib/src/ulib/__init__.pyc b/pyulib/src/ulib/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ef80e030d9d84203603f404d405b0191b12119f GIT binary patch literal 170 zcmcckiI*!(;Xpt#0~9a(*P0-8XzSl>_8$uJ~1aJ zJ{}?pmI4vQHb6o@BR@A)KPx#WF*8rUJikCcH$SB`C$(5Vue2mTKc`r~pt3Y4GfBU= hC|MuOh>y?A%PfhH*DI(j;Q$(71JPi|3^Eaj833+QCH?>a literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/all/__init__.py b/pyulib/src/ulib/all/__init__.py new file mode 100644 index 0000000..e58d681 --- /dev/null +++ b/pyulib/src/ulib/all/__init__.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""Module permettant d'importer automatiquement le contenu de tous les modules +de 'ulib.base'. + +Ce module doit être utilisé de cette manière: + + from ulib.all import * +""" + + +__all__ = [] + +# Liste des modules à importer automatiquement +try: + from ULIB_CONFIG import MODULES +except ImportError: + MODULES = ('encoding', 'base', 'env', 'uio', + 'output', 'input', 'encdetect', + 'procs', 'paths', 'htmlentities', 'dates', 'times', 'words', + 'tmpfiles', 'lines', 'files', 'config', 'editor', 'pager', + 'getopt', 'optparse', 'args', 'control', + 'functions', 'json', 'web', + 'flock', + ) +MODULES = list(MODULES) +def __has_module_and_remove(m, remove=True): + if m in MODULES: + if remove: MODULES.remove(m) + return True + return False + +from ulib.base.pversion import VersionMismatchError +from ulib.base.base import import__module__, import__all__ + +# Modules pour gérer les options +if __has_module_and_remove('getopt'): + __all__.extend(import__all__('getopt', globals(), locals(), 'getopt')) +if __has_module_and_remove('optparse'): + __all__.extend(import__module__('ulib.optparse', globals(), locals())) + __all__.extend(import__all__('ulib.optparse', globals(), locals(), 'OptionParser')) +if __has_module_and_remove('json'): + __all__.extend(import__module__('ulib.json', globals(), locals())) +if __has_module_and_remove('web'): + try: __all__.extend(import__module__('ulib.web', globals(), locals())) + except VersionMismatchError: pass + +# Autres modules à importer en entier +for name in MODULES: + module_name = 'ulib.base.%s' % name + try: __all__.extend(import__all__(module_name, globals(), locals())) + except VersionMismatchError: continue + +# Module control +if __has_module_and_remove('control', False): + import ulib.base.control + ulib.base.control.enable_exit_control() + +# Cleanup +__all__ = tuple(__all__) +del __has_module_and_remove +del VersionMismatchError +del import__module__ +del import__all__ +del name diff --git a/pyulib/src/ulib/all/__init__.pyc b/pyulib/src/ulib/all/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c3bdd4af803e25d2444d52ae167953121b8b70c GIT binary patch literal 2146 zcmb_d?@ruC5T8B6UCwt9NG>Eae{ezrr+(nRS8WxQkP3-FWD)?9A-!Z)bkH_t)Q3?vD58y9lc1F@9g+YZht% zF+d++fKtFk2Vwzn4UTJ&IM8=M?56WDAt&I-1w91bBtW&F||zxZ#)$DDETcE-i8WgH=R!tplB{;qh{SL?us0X0tKw%M~D(NHI1NBhAUIX@;u-AgUHtZp& zCD`tEi71~@yw!1$NH3H5P+A+N){B?bFw?n}xfd2z4@0X?3OSVM!oWzUR;Gm)OV8@U zpvibc&S6}%Ef)~^d>!HVa!X)uZFy42r`Ujt`K+Lv5z_(t1*jU@PcTksUq+^v1>;w*IaqF=4l)r`2 zwvJ<6*sQR25>=Iw*5sflmR3fkJ-z>~=JG8Gb9~j;_ zwIJ6C!#^m}$SR#04mmRDqOG6Fea?E2=;%1Y`v6AoD(Yqh#V4#Iz)>CaQxKod=n-{c4;+Bts44IP!iipVP2uu zJJdtjJBpG}rM)wq^(q4OQskRXOfS2j_V-L4^;kh3^nyU8$_Bwoc3~f4k{~z?O;FAZ z!ZZ#_G;|9Z7X^qZ(STVoDN4?C(6l3SgT;EQ46-^pT+4Hvmsn@eTQV&e;2m`;H$*B9p=KCjCy%frhGKa@6kT~~HoJ`WHiONG!KPxcPX;rl zueqVGPp81A$!uf7`m}SuHL@{3eFE)Ik8Dgyp23v#XUThy#C;M}CjS8nMnar3v`)o2 z{}H+8Nqj?s@ONp*#0-L1M?p~OTro>nK5VT29IS3_zFGgJBo7(a`I6xlFn94zu>MnY z_#O&Nh{mPHM4!UTH~weonD|L1k%9W|Dq!QL;Xm0WwWLK0Y%qvm`!Vub{Go S189m3M3)`N8^s`t1sDOW#4=U@ literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/args.py b/pyulib/src/ulib/base/args.py new file mode 100644 index 0000000..3864b50 --- /dev/null +++ b/pyulib/src/ulib/base/args.py @@ -0,0 +1,614 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Gestion des arguments de la ligne de commande. +""" + +__all__ = ('split_args', 'join_args', 'build_options', 'get_args', + 'Options', + ) + +try: True, False +except: True, False = 1, 0 + +import sys, re +from getopt import gnu_getopt + +from base import isstr, isbool, seqof, odict +from output import set_verbosity, VERBOSITY_OPTS +from input import set_interaction, INTERACTION_OPTS +from functions import apply_args + +RE_SPACES = re.compile(r'[ \t\r\n]+') +RE_QUOTE = re.compile(r'"') +RE_QQUOTE = re.compile(r'\\"') +RE_SPACES_OR_QUOTES = re.compile(r'[ \t\r\n"]+') +RE_TOKEN = re.compile(r'[^ \t\r\n"]+') +RE_QTOKEN = re.compile(r'"((?:\\"|[^"])*)"?') + +def has_spaces(cl): + return RE_SPACES.match(cl) is not None +def skip_spaces(pcl): + mo = RE_SPACES.match(pcl[0]) + if mo is not None: + pcl[0] = pcl[0][mo.end(0):] +def get_token(pcl): + token = None + mo = RE_TOKEN.match(pcl[0]) + if mo is not None: + token, pcl[0] = pcl[0][:mo.end(0)], pcl[0][mo.end(0):] + return token +def get_qtoken(pcl): + qtoken = None + mo = RE_QTOKEN.match(pcl[0]) + if mo is not None: + qtoken, pcl[0] = mo.group(1), pcl[0][mo.end(0):] + return qtoken + +def split_args(cl): + """Lire une chaine, et la découper en plusieurs arguments, à utiliser par + exemple avec getopt() ou get_args(). + + Note: les arguments peuvent être entre quotes, mais pour le moment, seul " + est supporté, pas '. + XXX ajouter le support de ' comme quote. + + @return: la liste des arguments, ou None si cl==None + @rtype: list + """ + if cl is None: return None + + args = [] + pcl = [cl] + while pcl[0]: + if has_spaces(pcl[0]): + skip_spaces(pcl) + if not pcl[0]: + break + + arg = '' + while pcl[0] and not has_spaces(pcl[0]): + if pcl[0][:1] == '"': + arg = arg + RE_QQUOTE.sub('"', get_qtoken(pcl)) + else: + arg = arg + get_token(pcl) + + args.append(arg) + + return args + +def join_args(args): + """L'opération inverse de split_args + + @return: une chaine, ou None si args==None + """ + if args is None: return None + i = 0 + for i in range(len(args)): + arg = args[i] + if not args or RE_SPACES_OR_QUOTES.search(arg) is not None: + args[i] = '"%s"' % RE_QUOTE.sub(r'\"', arg) + return ' '.join(args) + +def build_options(argsdesc): + """Construire une liste d'options pour utilisation avec get_args ou getopt. + + A partir d'une liste de termes (option, longoptions, desc), construire et + retourner (options, longoptions), où options est un chaine et longoptions + une liste, pour utilisation avec getopt() ou get_args(). + + @return: (options, longoptions) + @rtype: tuple + """ + options = '' + longoptions = [] + if argsdesc is not None: + for argdesc in argsdesc: + if argdesc[0:1] and argdesc[0] is not None: + options += argdesc[0] + if argdesc[1:2] and argdesc[1] is not None: + longopts = argdesc[1] + if isstr(longopts): longopts = (longopts,) + longoptions.extend(filter(None, longopts)) + return options, longoptions + +# options courtes à faire traiter par set_verbosity() ou set_interaction() +M_OPTIONS = {} +# options longues à faire traiter par set_verbosity() ou set_interaction() +M_LONGOPTIONS = {} +for _opt in VERBOSITY_OPTS: + if _opt.startswith('--'): M_LONGOPTIONS[_opt] = False + elif _opt.startswith('-'): M_OPTIONS[_opt] = False +for _opt in INTERACTION_OPTS: + if _opt.startswith('--'): M_LONGOPTIONS[_opt] = False + elif _opt.startswith('-'): M_OPTIONS[_opt] = False +del _opt + +RE_OPTION = re.compile(r'.:?') +def get_args(args=None, options=None, longoptions=None, **optdescs): + """frontend pour getopt qui reconnait les options de set_verbosity et + set_interaction(), et mets à jour les niveaux automatiquement. + """ + if args is None: args = sys.argv[1:] + if options is None: options = '' + longoptions = seqof(longoptions, []) + + options = RE_OPTION.findall(options) + longoptions = list(longoptions) + + def in_options(opt, options=options): + """Retourner True si l'option opt est mentionnée dans options, sans + tenir compte du fait qu'elle prend ou non un argument dans options. + + Si opt n'est pas mentionné dans options, l'y rajouter. + opt doit être de la forme 'o' ou 'o:' + """ + normopt = opt[:1] + for option in options: + normoption = option[:1] + if normopt == normoption: return True + options.append(opt) + return False + def in_longoptions(longopt, longoptions=longoptions): + """Retourner True si l'option longue longopt est mentionnée dans + longoptions, sans tenir compte du fait qu'elle prend ou non un argument + dans longoptions. + + Si longopt n'est pas mentionné dans longoptions, l'y rajouter. + longopt doit être de la forme 'longopt' ou 'longopt=' + """ + if longopt[-1:] == '=': normlongopt = longopt[:-1] + else: normlongopt = longopt + for longoption in longoptions: + if longoption[-1:] == '=': normlongoption = longoption[:-1] + else: normlongoption = longoption + if normlongopt == normlongoption: return True + longoptions.append(longopt) + return False + + # déterminer quelles options seront reconnues par set_verbosity. il s'agit + # de toutes celles qui ne sont pas traitées par l'utilisateur + m_options = M_OPTIONS.copy() + m_longoptions = M_LONGOPTIONS.copy() + + for m_option in m_options.keys(): + # m_option est de la forme '-o' + if not in_options(m_option[1:]): + m_options[m_option] = True + for m_longoption in m_longoptions.keys(): + # m_longoption est de la forme '--longopt' + if not in_longoptions(m_longoption[2:]): + m_longoptions[m_longoption] = True + + # appliquer les options reconnues par set_verbosity + options = ''.join(options) + optvalues, args = gnu_getopt(args, options, longoptions) + for i in range(len(optvalues)): + opt, _ = optvalues[i] + set_verbosity_or_interaction = False + if m_longoptions.get(opt, False): # long options + set_verbosity_or_interaction = True + elif m_options.get(opt, False): # options + set_verbosity_or_interaction = True + if set_verbosity_or_interaction: + if opt in VERBOSITY_OPTS: + set_verbosity(opt) + elif opt in INTERACTION_OPTS: + set_interaction(opt) + optvalues[i] = None + + # retourner les autres options qui n'ont pas été reconnues + return filter(None, optvalues), args + +################################################################################ + +_none = object() + +RE_PREFIX = re.compile(r'^-*') +RE_SUFFIX = re.compile(r'[:=]$') +RE_STUFF = re.compile(r'[^a-zA-Z0-9]') +def opt2name(opt): + """Obtenir un nom de variable dérivé d'un nom d'option + + Les tirets de début et les caractères : et = de fin sont supprimés, et les + caractères spéciaux sont remplacés par '_' + """ + name = RE_PREFIX.sub('', opt) + name = RE_SUFFIX.sub('', name) + name = RE_STUFF.sub('_', name) + return name + +class Option(object): + """Un objet stockant la description d'une option unique + + optdef définition de l'option, e.g. 'o', 'o:', 'long-option', ou + 'long-option=' + optname nom de l'option, e.g. 'o' ou 'long-option' + short est-ce une option courte? + takes_value + cette option prend-elle un argument? + + action action associée à cette option. + name nom de la variable associée à l'option. + """ + + _short, short = None, property(lambda self: self._short) + _optdef, optdef = None, property(lambda self: self._optdef) + _optname, optname = None, property(lambda self: self._optname) + _takes_value, takes_value = None, property(lambda self: self._takes_value) + + def __init(self, short, optdef, optname, takes_value): + self._short = short + self._optdef = optdef + self._optname = optname + self._takes_value = takes_value + + _action, action = None, property(lambda self: self._action) + _name, name = None, property(lambda self: self._name) + + LONGOPTION_PATTERN = r'(([a-zA-Z0-9$*@!_][a-zA-Z0-9$*@!_-]*)=?)' + RE_LONGOPTION0 = re.compile(r'--%s$' % LONGOPTION_PATTERN) + RE_LONGOPTION1 = re.compile(r'%s$' % LONGOPTION_PATTERN) + OPTION_PATTERN = r'(([a-zA-Z0-9$*@!_]):?)' + RE_OPTION0 = re.compile(r'-%s$' % OPTION_PATTERN) + RE_OPTION1 = re.compile(r'%s$' % OPTION_PATTERN) + + def __init__(self, optdef): + if not optdef: raise ValueError("optdef is required") + + mo = self.RE_LONGOPTION0.match(optdef) + if mo is not None: + self.__init(False, mo.group(1), mo.group(2), mo.group(1) != mo.group(2)) + else: + mo = self.RE_OPTION0.match(optdef) + if mo is not None: + self.__init(True, mo.group(1), mo.group(2), mo.group(1) != mo.group(2)) + else: + mo = self.RE_OPTION1.match(optdef) + if mo is not None: + self.__init(True, mo.group(1), mo.group(2), mo.group(1) != mo.group(2)) + else: + mo = self.RE_LONGOPTION1.match(optdef) + if mo is not None: + self.__init(False, mo.group(1), mo.group(2), mo.group(1) != mo.group(2)) + else: + raise ValueError("Invalid option: %s" % optdef) + + def __str__(self): + prefix = self._short and '-' or '--' + return '%s%s' % (prefix, self._optname) + str = __str__ + opt = property(__str__) + + def __repr__(self): + option = self.__str__() + if self._takes_value: + if self._short: option += ':' + else: option += '=' + return '%s(%s)' % (self.__class__.__name__, repr(option)) + repr = __repr__ + + def same_optdef(self, other): + return isinstance(other, Option) and self._optdef == other.optdef + def same_optname(self, other): + return isinstance(other, Option) and \ + self._optname == other.optname and \ + self._takes_value == other.takes_value + def __eq__(self, other): + if isstr(other): + return self.__str__() == other + elif isinstance(other, Option): + return self._optdef == other.optdef + else: + return False + + def set_action(self, action, name=None): + self._action = action + self._name = name + +class Action(object): + """Une action associée à une option quand elle est rencontrée sur la ligne + de commande. + + name nom de la variable associée à l'option, None s'il faut le calculer + initial si une valeur est associée à l'option, valeur initiale de cette + option. + + Cet objet doit implémenter une méthode __call__() qui prend les arguments + (option[, value[, options]]) + La méthode doit retourner False si elle veut indiquer qu'elle n'a pas pu + mettre à jour la valeur. Tout autre valeur indique le succès. + + option est une instance de Option. value est la valeur associée à l'option, + ou _none si l'option ne prend pas d'argument. options est l'instance de + l'objet Options qui analyse les arguments. + """ + + name = property(lambda self: None) + initial = property(lambda self: None) + + def __call__(self, option=None, value=_none, options=None): + pass + +class Options(object): + """Une classe permettant de traiter des arguments en ligne de commande. + + Son objectif est d'offrir une solution plus flexible que les fonctions + build_options et get_args() + + Avec le constructeur et la méthode add_option(), il est possible de + construire la liste des options valides. + + Ensuite, la méthode parse() permet d'analyser la ligne de commande. Par + défaut, si une méthode n'est pas définie pour une option, ou si la méthode + définie retourne False, initialiser une variable nommée d'après l'option, en + remplaçant sa valeur (si l'option prend un argument) ou lui ajoutant 1 (si + l'option ne prend pas d'argument). + """ + + class SetValue(Action): + """Mettre à jour une variable + + value valeur qu'il faut forcer, ou _none s'il faut prendre la valeur par + défaut. Si l'option prend un argument, la valeur par défaut est la + valeur spécifiée sur la ligne de commande. Sinon, il s'agit d'une + valeur incrémentée représentant le nombre de fois que l'option + apparait. + name nom de la variable à initialiser, ou None s'il faut dériver le nom + de la variable à partir du nom de l'option. + initial valeur initiale de la variable + """ + + _value = None + _name, name = None, property(lambda self: self._name) + _initial, initial = None, property(lambda self: self._initial) + + def __init__(self, value=_none, name=None, initial=None): + self._value = value + self._name = name + self._initial = initial + + def __call__(self, option=None, value=_none, options=None): + # nom: celui qui est spécifié dans le constructeur, ou un nom dérivé du + # nom de l'option + name = self._name + if name is None: name = opt2name(option.optname) + # valeur: celle qui est spécifiée dans le constructeur, ou alors laisser + # options sans charger + if self._value is not _none: value = self._value + + # mettre à jour la valeur + options.update_value(option, value) + + class CallMethod(Action): + _method = None + + def __init__(self, method=None): + self._method = method + + def __call__(self, option=None, value=None, options=None): + return apply_args(self._method, option, value, options) + + # type d'analyse: '+' pour s'arrêter à la première non option, '' sinon + _parseopt = None + + # liste d'options courtes, instances de Option + _soptions = None + + # liste d'options longues, instances de Option + _loptions = None + + # valeurs stockées dans cet objet + _values = None + + # dictionnaire des options définies, avec chacune une instance de Option + # associée + _options = None + + ############################################################################ + # Constructeur + + def __init__(self, *optdescs): + """Initialiser l'objet avec un ensemble d'argument de la forme + + (options, longoptions, desc) + + où options est une chaine avec des lettres de la forme 'o' ou 'o:', + longoptions une liste de chaines de la forme 'option' ou 'option=', et + desc une chaine quelconque. + + Ce format est pour assurer la compatibilité avec la fonction + build_options() + """ + super(Options, self).__init__() + object.__setattr__(self, '_parseopt', '') + object.__setattr__(self, '_soptions', []) + object.__setattr__(self, '_loptions', []) + object.__setattr__(self, '_values', {}) + object.__setattr__(self, '_options', {}) + + self.add_option(VERBOSITY_OPTS, set_verbosity) + self.add_option(INTERACTION_OPTS, set_interaction) + for optdesc in optdescs: + options = filter(None, optdesc[:2]) + desc = optdesc[2:3] and optdesc[2] or None + self.add_option(options, None, desc) + + def __option(self, opt): + """Obtenir l'instance de Option correspondant à l'argument + """ + if isinstance(opt, Option): return opt + if not opt.startswith('-'): + if len(opt) == 1: opt = '-' + opt + else: opt = '--' + opt + option = self._options.get(opt, None) + if option is None: raise ValueError("Unknown option: %s" % opt) + return option + + def add_option(self, options=None, action=None, desc=None): + """Ajouter une option + + options peut être une chaine de l'une des formes suivantes: + + '+' arrêter l'analyse à la première non-option (configuration de gnu_getopt) + 'o', '-o', 'o:', '-o:' + option courte sans et avec argument + 'longo', '--longo', 'longo=', '--longo=' + option longue sans et avec argument + + options peut aussi être une liste de ces chaines + """ + default_name = None + for opt in filter(None, seqof(options, ())): + # traiter la configuration de l'analyse '+' + if opt.startswith('+'): + self._parseopt = '+' + opt = opt[1:] + if not opt: continue + + # nom par défaut + if default_name is None: + default_name = opt2name(opt) + + # option + option = Option(opt) + + # action + if isinstance(action, Action): + # action déjà spécifiée + pass + elif action is None: + # pas d'action: mettre à jour la variable d'après le nom de la + # première option + action = Options.SetValue(name=default_name) + elif isstr(action): + # mettre à jour la variable nommée d'après l'action + action = Options.SetValue(name=action) + elif callable(action): + # appeler l'action + action = Options.CallMethod(action) + else: + raise ValueError("Unsupported action: %s" % repr(action)) + + name = action.name + if name is None: name = default_name + + option.set_action(action, name) + + # si une précédente option est définie, il faut la remplacer + self._soptions = filter(lambda soption: not soption.same_optname(option), self._soptions) + self._loptions = filter(lambda loption: not loption.same_optname(option), self._loptions) + + # nouvelle option + if option.short: self._soptions.append(option) + else: self._loptions.append(option) + self._options[option.opt] = option + + # valeur initiale + # ne spécifier la valeur initiale que si elle n'existe pas déjà + if not self.has_value(option): + self.set_value(option, action.initial) + + return self + + ############################################################################ + # Gestion des valeurs + + def __getitem__(self, key): + return self._values[key] + def __setitem__(self, key, value): + self._values[key] = value + def __delitem__(self, key): + del self._values[key] + def get(self, key, default=None): + return self._values.get(key, default) + def __getattr__(self, key, default=_none): + try: + if default is _none: return self._values[key] + else: return self._values.get(key, default) + except KeyError: raise AttributeError(key) + def __setattr__(self, key, value): + if self._values.has_key(key): self._values[key] = value + else: return super(Options, self).__setattr__(key, value) + def __delattr__(self, key): + try: del self._values[key] + except KeyError: raise AttributeError(key) + + def get_value(self, option, default=_none): + """Obtenir la valeur correspondant à l'option + """ + option = self.__option(option) + return self.get(option.name, default) + def has_value(self, option): + option = self.__option(option) + return self._values.has_key(option.name) + def set_value(self, option, value): + """Spécifier la valeur correspondant à l'option + """ + option = self.__option(option) + self._values[option.name] = value + return True + + def update_value(self, option, value=_none): + option = self.__option(option) + if value is _none: + if option.takes_value: + raise ValueError("Required value") + else: + value = self.get_value(option, None) + if value is None: value = 0 + self.set_value(option, value + 1) + else: + self.set_value(option, value) + + ############################################################################ + # Exploitation + + def get_args(self, args=None): + """Analyser les arguments à la recherche des options valides. Si + args==None, prendre sys.argv[1:] + + @return (optvalues, args) + + optvalues est une liste de tuple (opt, value) correspondant à toutes les + options qui ont été analysées par gnu_getopt(). args est la liste des + arguments qui ne sont pas des options. + """ + if args is None: args = sys.argv[1:] + soptions = self._parseopt + ''.join([option.optdef for option in self._soptions]) + loptions = [option.optdef for option in self._loptions] + optvalues, args = gnu_getopt(args, soptions, loptions) + return filter(None, optvalues), args + + _parsed_names = None + + def parse(self, args=None, optvalues=None): + """Traiter les options analysées par get_args(). Si optvalues==None, + analyser les arguments de args avec get_args() d'abord. + + @return (roptvalues, args) + + optvalues est une liste de tuple (opt, value) correspondant à toutes les + options qui ont été analysées, mais n'ont pas pu être traitées par cet + objet. + + args est la liste des arguments qui ne sont pas des options. + """ + self._parsed_names = {} + if optvalues is None: optvalues, args = self.get_args(args) + roptvalues = [] + for opt, value in optvalues: + option = self.__option(opt) + self._parsed_names[option.name] = True + if not option.takes_value: value = _none + if option.action(option, value, self) == False: + roptvalues.append((opt, value)) + self.update_value(option, value) + return roptvalues, args + + def was_parsed(self, name): + """Indiquer si une option correspondant à la variable name a été + mentionnée sur la ligne de commande. + """ + if self._parsed_names is None: return False + return self._parsed_names.has_key(name) diff --git a/pyulib/src/ulib/base/args.pyc b/pyulib/src/ulib/base/args.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8d659effba5c140d206270c31b4791b24e88cc8 GIT binary patch literal 25491 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 0: path = "obj." + ".".join(names[:i]) + else: path = "None" + raise AttributeError("%s instance has no value '%s'" % (path, name)) + else: break + value = getattr(value, name) + return value + +def setattrs(obj, names, value): + u"""Soit un objet obj, et un nom de la forme "attr0.attr1....", + effectuer l'équivalent de l'opération: + + obj.attr0.attr1.... = value + """ + if not isseq(names): names = names.split(".") + __check_names(names) + obj = getattrs(obj, names[:-1], True) + setattr(obj, names[-1], value) + +def delattrs(obj, names): + u"""Soit un objet obj, et un nom de la forme "attr0.attr1....", + effectuer l'équivalent de l'opération: + + del obj.attr0.attr1.... + """ + if not isseq(names): names = names.split(".") + __check_names(names) + obj = getattrs(obj, names[:-1], True) + delattr(obj, names[-1]) + +def make_delegate(names, getter=True, setter=True, deleter=False): + if getter is True: + def getter(self): + return getattrs(self, names, True) + if setter is True: + def setter(self, value): + setattrs(self, names, value) + if deleter is True: + def deleter(self): + delattrs(self, names) + + accessors = {} + if getter: accessors['fget'] = getter + if setter: accessors['fset'] = setter + if deleter: accessors['fdel'] = deleter + return property(**accessors) + +def get__all__(module): + """Retourner la valeur __all__ d'un module, ou la construire si cette + valeur n'est pas définie. + + @rtype: tuple + """ + all = getattr(module, '__all__', None) + if all is None: + all = [] + for key in module.__dict__.keys(): + if key[0] != '_': all.append(key) + return tuple(all) + +def import__module__(module_name, globals, locals=None, name=None): + """Importer dans globals le module nommé module_name, en le nommant name. + + Par défaut, name est le nom de base du module. par exemple, le module + "a.b.c" sera importé sous le nom "c" + """ + module = __import__(module_name, globals, locals) + basenames = module_name.split('.') + for basename in basenames[1:]: + module = getattr(module, basename) + + if name is None: name = basenames[-1] + globals[name] = module + return [name] + +def import__all__(module_name, globals, locals=None, *names): + """Importer dans globals tous les objets du module nommé module_name + mentionnés dans names. Si names est vides, tous les objets sont importés + comme avec 'from module import *' + """ + module = __import__(module_name, globals, locals) + basenames = module_name.split('.') + for basename in basenames[1:]: + module = getattr(module, basename) + + if not names: names = get__all__(module) + __all__ = [] + for name in names: + globals[name] = getattr(module, name, None) + __all__.append(name) + return __all__ diff --git a/pyulib/src/ulib/base/base.pyc b/pyulib/src/ulib/base/base.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c5e37dc5db63f6c6f9f93f9af48742a6899ab8b GIT binary patch literal 23352 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 D8!a8D literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/config.py b/pyulib/src/ulib/base/config.py new file mode 100644 index 0000000..94c64ee --- /dev/null +++ b/pyulib/src/ulib/base/config.py @@ -0,0 +1,811 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Fonctions utilitaires pour lire des fichiers de configuration. + +Dans un fichier de configuration, l'on reconnait des lignes de la forme:: + + [comment][prefix]varname=value + +value peut être placé entre double quotes ou simple quotes. Elle peut s'étendre sur +plusieurs lignes si elle est mise entre quotes, ou si elle se termine par \ +""" + +__all__ = ('ConfigFile', 'ShConfigFile', 'PListFile') + +import os, string, re, types +from os import path + +from base import make_prop, isseq, seqof, firstof +from uio import _s, _u +from files import TextFile + +#################### +# gestion des commentaires + +re_comments = { + 'shell': re.compile(r'[ \t]*#+'), + 'conf': re.compile(r"[ \t]*;+"), + 'C': re.compile(r'[ \t]*//+'), + 'visual basic': re.compile(r"[ \t]*'+"), + 'wincmd': re.compile(r'[ \t]*(?:r|R)(?:e|E)(?:m|M)'), + } + +def is_comment(s, type=None): + """Retourner vrai si s un commentaire (c'est à dire si la ligne commence par + un des styles de commentaires supportés) + """ + comment_types = type is None and re_comments.values() or [re_comments[type]] + for comment_type in comment_types: + if comment_type.match(s): return True + return False + +#################### +# gestion des fichiers de configuration + +_marker = object() + +class ConfigFile(TextFile): + r"""Un fichier de configuration, que l'on doit lire sous Python, et que l'on + doit partager éventuellement avec d'autres langages ou d'autres systèmes + d'exploitation. Par exemple, il peut s'agir d'un fichier de configuration + sous bash. + + Une valeur non quotée est trimée à droite et à gauche. Une valeur quotée + n'est jamais trimée. + + Une valeur quotée peut être suivie d'une valeur non quotée, et les deux sont + mergés. Mais une fois que l'on commence à parser une valeur non quotée, plus + aucun traitement n'est effectuée, ce qui fait qu'une valeur quotée ne peut + pas suivre une valeur non quotée (cf le "andme" ci-dessus). + + Ceci diffère du comportement de parseur plus évolués comme celui de bash. On + considère néanmoins que c'est une caractéristique, non un bug. XXX corriger + ce problème, ne serait-ce que pour supporter la lecture de fichiers tels que + var='value'\''with a quote' + + Tests + ===== + + >>> from StringIO import StringIO + >>> input = StringIO(r'''# comment + ... name=value + ... name2= value + ... name3 = value + ... qname="qvalue" + ... qname2=" qvalue " + ... qname3 = " qvalue " + ... qname4=" + ... multi-line + ... qvalue + ... " + ... fancy="\ + ... noNL\ + ... "foryou"andme" + ... quote='"' + ... quote2="\"" + ... quote3='\'' + ... quote4='\\' + ... quote5='\\\'' + ... quote6='\\\'remainder' + ... ''') + >>> from ulib.base.config import ConfigFile + >>> cf = ConfigFile(input) + >>> cf.get_string('name') + u'value' + >>> cf.get_string('name2') + u'value' + >>> cf.get_string('name3') + u'value' + >>> cf.get_string('qname') + u'qvalue' + >>> cf.get_string('qname2') + u' qvalue ' + >>> cf.get_string('qname3') + u' qvalue ' + >>> cf.get_string('qname4') + u'\n multi-line\n qvalue\n ' + >>> cf.get_string('fancy') + u'noNLforyouandme' + >>> cf.get_string('quote') + u'"' + >>> cf.get_string('quote2') + u'\\"' + >>> cf.get_string('quote3') + u"\\'" + >>> cf.get_string('quote4') + u'\\\\' + >>> cf.get_string('quote5') + u"\\\\\\'" + >>> cf.get_string('quote6') + u"\\\\\\'remainder" + + """ + + # valeurs lues dans le fichier de configuration + _items, items = make_prop('_items')[:2] + # valeurs par défaut + _defaults, defaults = make_prop('_defaults')[:2] + # expression régulière identifiant le préfixe des variables + _prefix, prefix = make_prop('_prefix', '')[:2] + # expression régulière identifiant pour le séparateur entre le nom de la + # variable et sa valeur. + _equals, equals = make_prop('_equals', r'\s*=')[:2] + # faut-il considérer les variables en commentaires? + _comment, comment = make_prop('_comment')[:2] + + ############################################################################ + # interface publique + + def __init__(self, file=None, defaults=None, + prefix=None, equals=None, comment=False, + raise_exception=True, lines=None): + """ + @param prefix une expression régulière identifiant un préfixe mentionné + avant chaque variable. par exemple, si prefix=='##@' et qu'on + cherche la variable value, alors la ligne ##@value est cherchée. + @param comment faut-il considérer les valeurs qui sont en commentaires? + Si oui, tout se passe comme si le commentaire n'existe pas. + @param defaults un ensemble de valeurs par défaut qui sont retournées si la + variable n'existe pas dans le fichier. + @param lines instance de Lines ou BLines permettant de décoder le contenu du + fichier. + """ + super(ConfigFile, self).__init__(file, raise_exception=raise_exception, lines=lines) + self._items = {} + self._defaults = defaults or {} + if prefix is not None: self._prefix = prefix + if equals is not None: self._equals = equals + self._comment = comment + + def __getitem__(self, name, default=_marker): + """Obtenir la valeur de la variable name, telle qu'elle a été lue. + Si c'est un tableau, retourner une liste. Sinon retourner une chaine. + + Si la variable n'est pas définie, retourner default. + """ + if not self._items.has_key(name): self._load_value(name) + if default is _marker: + if not self._items.has_key(name) and self._defaults.has_key(name): + return self._defaults[name] + return self._items[name] + return self._items.get(name, default) + get = __getitem__ + + def __setitem__(self, name, value): + self._items[name] = value + + def __delitem__(self, name): + del self._items[name] + + def has_key(self, name): + try: self.__getitem__(name) + except KeyError: return False + else: return True + + def get_string(self, name, default=_marker): + """Obtenir la valeur de la variable name. Si la variable est un tableau, + retourner la première valeur de ce tableau. Retourner None si le tableau + est vide. + """ + value = self.__getitem__(name, default) + if isseq(value): return firstof(value) + else: return value + + def get_lines(self, name, strip=False, default=_marker): + """Obtenir une valeur avec get_string(), et la spliter sur le caractère + de fin de ligne. Retourner la liste des lignes. + + si strip est vrai, on strip toutes les lignes puis on enlève les + lignes vides. + """ + lines = self.get_string(name, default) + if not isseq(lines): lines = re.split(r'(?:\r?)\n', lines) + if strip: lines = filter(None, map(string.strip, lines)) + return lines + + def get_paths(self, name, strip=False, default=_marker): + """Obtenir une valeur avec get_string(), la splitter sur le caractère + 'os.path.pathsep'. Retourner la liste des chemins. + + si strip est vrai, on strip toutes les valeurs puis on enlève les + valeurs vide. + """ + paths = self.get_string(name, default) + if not isseq(paths): paths = paths.split(path.pathsep) + if strip: paths = filter(None, map(string.strip, paths)) + return paths + + def get_array(self, name, default=_marker): + """Obtenir la liste des valeurs de la variable name. Si name est une + valeur scalaire, retourner une liste d'un seul élément. + """ + return list(seqof(self.__getitem__(name, default))) + + ############################################################################ + # partie privée + + RE_ANTISLASHES = re.compile(r'\\+$') + def _is_cont(self, value): + """Tester si value doit être fusionné avec la ligne suivante à cause de + la présence d'un caractère de continuation de ligne. + + Par défaut, on teste si value se termine par un nombre impair de '\\' + """ + mo = self.RE_ANTISLASHES.search(value) + if mo is None: return False + return len(mo.group()) % 2 == 1 + + def _strip_cont(self, value): + """Enlever le caractère de continuation de ligne de value. On assume que + self._is_cont(value) est vrai. + """ + return value[:-1] + + def _merge_cont(self, index, value, sep=''): + """Merger value située à la ligne index, et la ligne suivante, en les + séparant par sep. On assume que self._is_cont(value) est vrai, et que le + caractère de continuation a été enlevé avec self._strip_cont(value) + + Dans la valeur de retour, eof vaut True si la fin de fichier est + rencontrée. + + @return (index+1, merged_value, eof) + """ + if index + 1 < len(self.lines): + index += 1 + value = value + sep + self.lines[index] + eof = False + else: + eof = True + return index, value, eof + + def _unescape(self, value, quote=''): + """Traiter les séquences d'échappement dans une valeur scalaire. Si la + valeur était quotée, quote contient la valeur du caractère ("'", '"' ou + ''). Par défaut, ne rien faire. + + Cette fonction doit être surchargée en fonction du type de fichier de + configuration que l'on lit. + + La valeur quote=='' signifie que la valeur n'était pas quotée, mais il + peut quand même y avoir des séquences d'échappement à traiter. + """ + return value + + def _load_value(self, name): + """charger la valeur d'une variable depuis le fichier. + + XXX rendre le parcours plus robuste: faire attention à ne pas lire une + valeur à l'intérieur d'une autre valeur. Par exemple: + + var1="\ + var2=bad + " + var2=good + + Avec l'implémentaion actuelle, si on demande la valeur de var2, on + obtient bad. Une façon de corriger cela de parcourir *tout* le fichier, + de lire les valeurs non analysées de chaque variable au fur et à mesure, + puis de les placer en cache. ensuite, _load_value() se contenterai + d'analyser les valeurs dans le cache. + + @return None si la valeur n'est pas trouvée dans le fichier. Sinon, + retourner une valeur scalaire ou une séquence en fonction du type de la + valeur. + """ + # le groupe 1 sera testé pour voir si c'est un commentaire + re_varname = re.compile(r'(.*)%s%s%s' % (self._prefix, name, self._equals)) + re_value = re.compile(r'.*%s%s%s(.*)' % (self._prefix, name, self._equals)) + + indexes = self.grepi(re_varname) + if not indexes: return None + + # trouver d'abord la ligne appropriée + comment = '' + for index in indexes: + comment = re_varname.match(self.lines[index]).group(1) + if is_comment(comment): + # si la valeur est en commentaire, ne l'accepter que si + # self._comment est vrai + if not self._comment: + continue + # nous avons trouvé l'index de la ligne + break + else: + # aucune ligne n'a été trouvée + return + + # ensuite lire la valeur + value = re_value.match(self.lines[index]).group(1) + value = self._parse_logic(index, value) + self._items[name] = value + + def _parse_logic(self, index, value): + """Implémenter la logique d'analyse de la valeur d'une variable. + + Il faut reimplémenter cette méthode si on veut modifier le type de + valeurs supportées. _parse_scalar() permet d'analyser une valeur simple, + _parse_array() permet d'analyser un tableau de valeurs. + + Par défaut, on ne supporte que les valeurs scalaire. Utiliser + ShConfigFile pour supporter les tableaux. + """ + value = value.lstrip() # ignorer les espaces avant la valeur + return self._parse_scalar(index, value) + + ## valeurs scalaires simples + + RE_SPACES = re.compile(r'\s+') + def _parse_scalar(self, index, value): + remainder = value + value = '' + lstrip = None + rstrip = None + while remainder: + mo = self.RE_SPACES.match(remainder) + if mo is not None: + # ne pas supprimer les espaces entre les valeurs + remainder = remainder[mo.end():] + value += mo.group() + # XXX supporter de spécifier le type de commentaires valides dans ce + # fichier de configuration. A cet endroit, il faudrait pouvoir + # éliminer les commentaires qui sont sur la ligne. évidemment, ce ne + # serait pas forcément approprié suivant la configuration. exemple: + # REM pour un fichier cmd n'est valide qu'en début de ligne. + elif self._is_quoted(remainder): + # valeur quotée. pas de strip + if lstrip is None: lstrip = False + rstrip = False + index, next_value, remainder = self._parse_quoted(index, remainder) + value += self._unescape(next_value) + else: + # valeur non quotée. lstrip si en premier. rstrip si en dernier + if lstrip is None: lstrip = True + rstrip = True + index, next_value, remainder = self._parse_value(index, remainder) + value += self._unescape(next_value) + if lstrip: value = value.lstrip() + if rstrip: value = value.rstrip() + return value + + RE_VALUE = re.compile('[^\\s\'"]*') + def _parse_value(self, index, value, pattern=None): + """Parser une valeur simple non quotée à partir de value (qui se trouve + à la position index) et des lignes suivant index si la ligne se termine + par '\\'. + + @return index, value, remainder + """ + while self._is_cont(value): + value = self._strip_cont(value) + index, value, eof = self._merge_cont(index, value) + if eof: break + if pattern is None: pattern = self.RE_VALUE + mo = pattern.match(value) + if mo is None: + return index, '', value + else: + remainder = value[mo.end():] + value = value[:mo.end()] + return index, value, remainder + + ## valeurs scalaires quotées + + def _is_quoted(self, value): + """Tester si value est le début d'une valeur quotée. Ignorer les espaces + avant la quote. + """ + return value.lstrip()[:1] in ('"', "'") + + def _search_next_quote(self, value, re_quote): + """Chercher un match de re_quote dans value, qui ne soit pas précédé par + un nombre impair de '\\'. + """ + pos = 0 + while True: + mo = re_quote.search(value, pos) + if mo is None: return None + if self._is_cont(value[:mo.start()]): + # nombre impair de '\\', la quote est mise en échappement + pos = mo.end() + else: + return mo + + RE_QUOTE = re.compile(r'[\'"]') + def _parse_quoted(self, index, value): + """Parser une valeur quotée à partir de value (qui se trouve à la + position index) et des lignes suivant index. + + value *doit* commencer par la quote. si _is_quoted(value) est vrai, il + faut enlever les espaces éventuels au début de value avant de la passer + à cette méthode. + + @return index, value, remainder + """ + if self.RE_QUOTE.match(value) is None: + raise ValueError("value must start with a quote, got %s" % repr(_s(value))) + quote, value = value[:1], value[1:] + re_quote = re.compile(quote) + mo = self._search_next_quote(value, re_quote) + while mo is None: + if self._is_cont(value): + value = self._strip_cont(value) + index, value, eof = self._merge_cont(index, value) + else: + index, value, eof = self._merge_cont(index, value, self.nl) + mo = self._search_next_quote(value, re_quote) + if eof: break + if mo is None: + # valeur quotée, mais mal terminée. on fait comme si on a rien vu + return index, value, '' + else: + remainder = value[mo.end():] + value = value[:mo.start()] + return index, value, remainder + + ## tableaux + + def _is_array(self, value): + """Tester si value est le début d'un tableau. Ignorer les espaces avant + le tableau. + """ + return False + + def _parse_array(self, index, value): + """Parser un tableau à partir de value (qui se trouve à la position + index) et des lignes suivant index. + + value *doit* commencer par le tableau. si _is_array(value) est vrai, il + faut enlever les espaces éventuels au début de value avant de la passer + à cette méthode. + """ + return [] + +class ShConfigFile(ConfigFile): + r"""Un fichier de configuration qui est susceptible d'être lu aussi par bash + (ou tout autre shell sh-like). On supporte l'évaluation de variables, et + certaines séquences d'échappement pour des valeurs quotées. + + Il y a certaines limitations: lors de la lecture des valeurs des variables, + les caractères sont traduits suivant la correspondance suivante: + + \ en fin de ligne: continuer sur la ligne suivante + \" " + \\ \ + \$ $ + + La séquence \` n'est pas traduite. En effet, pour que cela aie un sens, il + faudrait que l'on traduise aussi `cmd` + + De plus, on ne supporte que les variables de la forme $var et ${var} + + Tests + ===== + + >>> from StringIO import StringIO + >>> input = StringIO(r'''# comment + ... var1=value + ... var2="value" + ... var3='value' + ... var4=(value1 "value2" 'value3') + ... var5=( + ... value1 + ... "value2\ + ... " 'value3' + ... ) + ... var6=() + ... var7=( ) + ... var8=( + ... ) + ... ''') + >>> from ulib.base.config import ShConfigFile + >>> cf = ShConfigFile(input) + >>> cf.get_string('var1') + u'value' + >>> cf.get_string('var2') + u'value' + >>> cf.get_string('var3') + u'value' + >>> cf.get_string('var4') + u'value1' + >>> cf.get_array('var4') + [u'value1', u'value2', u'value3'] + >>> cf.get_array('var5') + [u'value1', u'value2', u'value3'] + >>> [cf.get_array(name) for name in ('var6', 'var7', 'var8')] + [[], [], []] + >>> cf.get_array('var1') + [u'value'] + >>> cf.get_string('var4') + u'value1' + >>> cf.get_string('var6') is None + True + """ + + RE_VAR = re.compile(r'\$(?:\{([^}]+)\}|(\w+))') + TRANS_MAP = {r'\"': '"', r'\\': '\\', r'\$': '$'} + + def __convert(self, value): + # XXX rendre la conversion plus robuste: veiller à l'ordre ('\\\\' en + # dernier...), et ne faire la conversion que pour un nombre impaire de + # '\\'. + for s, r in self.TRANS_MAP.items(): + value = value.replace(s, r) + return value + + def _unescape(self, value, quote=''): + """convertir une valeur quotée, suivant les règles de bash. + quote peut valoir "'", '"', '' + """ + # aucune traduction entre '' + if quote == "'": return value + # sinon appliquer les règles standards. notamment, remplacer $var et + # ${var} par self._items["var"] ou os.environ["var"] + splited = self.RE_VAR.split(value) + value = self.__convert(splited[0]) + splited = splited[1:] + while splited: + var0 = splited[0] + var1 = splited[1] + text = splited[2] + splited = splited[3:] + var = var0 or var1 + if self.has_key(var): value = value + self.get_string(var) + else: value = value + os.environ.get(var, "") + value = value + self.__convert(text) + return value + + def _parse_logic(self, index, value): + value = value.lstrip() # ignorer les espaces avant la valeur + if self._is_array(value): return self._parse_array(index, value) + else: return self._parse_scalar(index, value) + + ## tableaux + + def _is_array(self, value): + """Tester si value est le début d'un tableau. + """ + return value.strip()[:1] == '(' + + RE_ARRAY_VALUE = re.compile('[^\\s\'")]*') + def _parse_next_scalar(self, index, value): + """Parser la prochaine valeur scalaire + XXX à faire + @return index, value, remainder + """ + remainder = value + value = '' + lstrip = None + rstrip = None + while remainder: + if self.RE_SPACES.match(remainder) is not None: + # les valeurs sont séparées par des espaces + break + # XXX cf ConfigFile._parse_scalar pour la gestion des commentaires + elif self.RE_EOA.match(remainder) is not None: + # fin de tableau + break + elif self._is_quoted(remainder): + # valeur quotée. pas de strip + if lstrip is None: lstrip = False + rstrip = False + index, next_value, remainder = self._parse_quoted(index, remainder) + value += self._unescape(next_value) + else: + # valeur non quotée. lstrip si en premier. rstrip si en dernier + if lstrip is None: lstrip = True + rstrip = True + index, next_value, remainder = self._parse_value(index, remainder, self.RE_ARRAY_VALUE) + value += self._unescape(next_value) + if lstrip: value = value.lstrip() + if rstrip: value = value.rstrip() + return index, value, remainder + + RE_SOA = re.compile(r'\(') + RE_EOA = re.compile(r'\)') + def _parse_array(self, index, value): + """Parser un tableau à partir de value (qui se trouve à la position + index) et des lignes suivant index. + + @return index, values, remaining + """ + if self.RE_SOA.match(value) is None: + raise ValueError("value must start with '(', got %s" % repr(_s(value))) + remainder = value[1:] + values = [] + eoa = False # end of array + while True: + if not remainder: + # nous n'avons pas encore rencontré la fin du tableau. Lire les + # lignes jusqu'à ce que nous trouvions ce qui est nécessaire + index, remainder, eof = self._merge_cont(index, remainder) + if eof: break + # ignorer les espaces entre les valeurs + mo = self.RE_SPACES.match(remainder) + if mo is not None: + remainder = remainder[mo.end():] + continue + # tester si on arrive à la fin du tableau + if self.RE_EOA.match(remainder) is not None: + remainder = remainder[1:] + eoa = True + break + # parser une valeur scalaire + index, next_value, remainder = self._parse_next_scalar(index, remainder) + values.append(next_value) + # ici, eoa vaut True si le tableau a été terminé proprement. + # sinon, on fait comme si on a rien vu. + return values + +_debug = False +def _print_debug(s): + if _debug: print s + +class PListFile(TextFile): + def readlines(self, raise_exception=True, close=True): + TextFile.readlines(self, raise_exception, close) + + self.items = None + self.list = None + self.value = None + + if self.is_valid(): + if self.lines and self.lines[0][:5] == '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 BbxHsL literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/control.py b/pyulib/src/ulib/base/control.py new file mode 100644 index 0000000..0008566 --- /dev/null +++ b/pyulib/src/ulib/base/control.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Contrôle d'exécution. +""" + +__all__ = ('Status', 'OK_STATUS', 'ERROR_STATUS', + 'Exit', 'exit', 'die') + +import sys + +from base import isnum +from output import eerror + +class Status: + """Un objet qui peut être utilisé pour retourner un status pour l'exécution + d'un programme ou d'une opération. + + Le status peut être OK ou ERROR. La valeur booléenne de cet objet est True + ou False en fonction de la valeur du status. Un code de retour de programme + associé est disponible avec la propriété exitcode. + """ + + ok = True + exitcode = 0 + def __init__(self, ok=None, exitcode=None): + if ok is not None: + self.ok = ok + if exitcode is None: + if ok: exitcode = 0 + else: exitcode = 1 + self.exitcode = exitcode + elif exitcode is not None: + self.ok = exitcode == 0 + self.exitcode = exitcode + + def __nonzero__(self): + return self.ok + +OK_STATUS = Status(True) +ERROR_STATUS = Status(False) + +def ensure_status(status): + """Retourner une instance de Status. + + Si status est un entier, le prendre comme valeur de retour. + Sinon, le considérer comme une valeur booléenne indiquant le status: OK ou ERREUR. + """ + if isinstance(status, Status): return status + elif isnum(status): return Status(exitcode=int(status)) + else: return Status(status) + +class Exit(Exception): + """Classe qui peut être utilisée pour propager un code de retour quand on + quitte l'application. + """ + def __init__(self, status): + self.exitcode = ensure_status(status).exitcode + +def exit(status=OK_STATUS): + """Quitter le programme avec l'exception Exit, en propageant par défaut le + status OK_STATUS. + """ + raise Exit(status) + +def die(msg=None, status=ERROR_STATUS): + """Quitter le programme en affichant un message d'erreur, en propageant + par défaut le status ERROR_STATUS. + + Si msg==None, on prend le message de la dernière exception capturée. + """ + eerror(msg) + raise Exit(status) + +def enable_exit_control(): + """Installer un excepthook qui appelle sys.exit() quand on lève l'exception + Exit. + """ + prev_excepthook = sys.excepthook + if prev_excepthook is sys.__excepthook__: + prev_excepthook = None + def exit(cls, obj, tb, prev_excepthook=prev_excepthook): + if issubclass(cls, Exit): sys.exit(obj.exitcode) + elif prev_excepthook is not None: + prev_excepthook(cls, obj, tb) + else: sys.__excepthook__(cls, obj, tb) + sys.excepthook = exit diff --git a/pyulib/src/ulib/base/control.pyc b/pyulib/src/ulib/base/control.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d8692c17584d1a870cabc708aeca982e63cb3e3 GIT binary patch literal 4053 zcmcIn-EJF26h3PwPVCrC+J-{;5qpDbpo*oUUVz%Fv=j-oN@3liVj-iep)^^&$oojh!&&-~gIdgu#bM~*ld!6t7-@lEi{w?8o z6V3dFF2UcUJ)#kaH5z%7nFfs-LNC)^ld=UGEs)3DZQ5I;tVN?1#$Y*1Nr#Sz4vBsk zEs?^1nGQV~wT12oy+X+fGmW~GERyQcHV*1emz^QirldzlShmUnDJ(ccM-T$rugc1E zJU{H8gV5#bd7{U=rG7pk)U$*;Cler3}vH7Cb@p{vp_mBk}hrC;T~kt@0v*(LS# zo1J|BF0T@GZTR;he?Q6; zMA*_gJ2_T)uKYy#F(h_!D`Wi!Wu;_8Ec`mkjPh0PZ|gkf^O!xGZj)5?ksCg)9&5bB z-`$U?spzs_WQ>keXn{{jQd8(W-O7|7?W>qK1$R;6y=4}dk@s`=OQB$j*-YFTXt&YK zQ*_V7R=aJ|H^{@_8BvH{ynA<<^m{U^J8)-@o;TDXKV5bf`yb{fI<|e4Y zV?9=b-8hTVeDISl24kI6nKFaCvap&N6bDt7ZVgNs4}{o?j2gr`x24WDiUT<_4AVTd zVfZdKH0$Wd>v?UoCF*z`JbT`%7qi@xCJJTnz%_BJp#;mO^dhRGR=_ZCd3nz;%ys^= zDs>osgrQ;ek+<1rCDj+oqp=D@%Nh$qrxOfW;xJ5f44T8Aop+|%fb}0RU<5sMGs%UT zi@F2sORH#xC2K9U8ZEEot*%U!%TkGQzXD-Jzt*F}2I)n388q#|S?~m?>UHw)4vq>0 zb5n=pfkE6F3`2OX^E~q>b_-4ciSpANSdzyQf(|%Ofx|GJfCC2~P(|f7Rpn(L@mG{8 zPk>UfW{{~7Vj9P8lVJ)22{$~=qzUi`_~lkqK#|vRCC!ucNfqUmk8mjWg+GO(I}Zc5 zv4p^8pS2{mP0i#UqS=7qTJSv0?F_)(elvT{#gvwPMCGO`Rp<`*6cb`sYOT?t*Q0eW z#+3Xxa3pEG4dQgtfccJ)Lz|BoqmvovQYSY~WerM^#{fGE3_f0d6$!7&*uymz7%;#BF4`J& z*aSnpsP*zR7EHz_Z+@Sr{)Y&qqmVXjmX76(^8s|!0gZx{xm6}~c;x~?MN!*ykg$_5g=7*cSSJI>4U z%_rY30Y{ajH~EUZGeK$b8fT1U=HRdlK14Uus!($=x>T^&T+Moe1u_h 11: + month -= 12 + year += 1 + while month < 0: + month += 12 + year -= 1 + month += 1 + return year, month +MONTHDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] +def _monthdays(year, month, offset=0): + year, month = _fix_month(year, month + offset) + if month == 2 and _isleap(year): leapday = 1 + else: leapday = 0 + return MONTHDAYS[month] + leapday +def _fix_day(year, month, day): + # on assume que month est déjà "fixé" + day -= 1 + while day > _monthdays(year, month) - 1: + day -= _monthdays(year, month) + year, month = _fix_month(year, month + 1) + while day < 0: + year, month = _fix_month(year, month - 1) + day += _monthdays(year, month) + day += 1 + return year, month, day +def _fix_date(day, month, year): + year, month = _fix_month(year, month) + year, month, day = _fix_day(year, month, day) + return day, month, year + +MONTHNAMES = [u"Janvier", u"Février", u"Mars", u"Avril", u"Mai", u"Juin", + u"Juillet", u"Août", u"Septembre", u"Octobre", u"Novembre", u"Décembre", + ] +MONTHNAMES3 = [u"Jan", u"Fév", u"Mar", u"Avr", u"Mai", u"Jun", + u"Jul", u"Aoû", u"Sep", u"Oct", u"Nov", u"Déc", + ] +MONTHNAMES1 = [u"J", u"F", u"M", u"A", u"M", u"J", + u"J", u"A", u"S", u"O", u"N", u"D", + ] + +class Date(object): + """Un wrapper pour 'datetime.date'. + + Attention! Cet objet est mutable, il ne faut donc pas l'utiliser comme clé + dans un dictionnaire. + """ + _d = None + + def __init__(self, day=None, month=None, year=None, t=None): + """Initialiser l'objet. + + Dans l'ordre, les champs considérés sont: + - day si c'est une instance de Date ou datetime.date + - t le nombre de secondes depuis l'epoch, comme retourné par + time.time(). Cette valeur est fusionnée avec les valeurs numériques + day, month, year. + """ + if day is not None and not isnum(day) and month is None and year is None and t is None: + if isinstance(day, pydate): self._d = day + elif isinstance(day, Date): self._d = day._d + if self._d is None: + if t is None: t = time() + y, m, d = localtime(t)[:3] + if year is None: year = y + if month is None: month = m + if day is None: day = d + day, month, year = _fix_date(day, month, year) + self._d = pydate(year, month, day) + + date = property(lambda self: self._d) + year = property(lambda self: self._d.year) + month = property(lambda self: self._d.month) + day = property(lambda self: self._d.day) + + # nombre de jours du mois + monthdays = property(lambda self: MONTHDAYS[self.month]) + + def weekday(self): + """Retourner le jour de la semaine, de 0 (lundi) à 6 (dimanche) + """ + return self._d.weekday() + def isoweekday(self): + """Retourner le jour de la semaine, de 1 (lundi) à 7 (dimanche) + """ + return self._d.isoweekday() + def is_today(self): + """Tester si cette date est le jour d'aujourd'hui + """ + now = self.__class__()._d + date = self._d + return now.year == date.year and now.month == date.month and now.day == date.day + + def calday(self, show_month=False, show_year=False): + """Retourner 'day' si day != 1 and not show_month and not show_year, + 'day/month' si month != 1 and not show_year, + 'day/month/year' sinon + """ + day, month, year = self.day, self.month, self.year + if day != 1 and not show_month and not show_year: return _u(day) + elif month != 1 and not show_year: return u"%i/%i" % (day, month) + else: return u"%i/%i/%i" % (day, month, year) + + def monthname(self, format=None): + """Obtenir le nom du mois. + Si format est dans (1, 't', 'tiny'), retourner le nom sur 1 lettre. + Si format est dans (3, 's', 'small'), retourner le nom sur 3 lettres. + Sinon, retourner le nom complet. + """ + if format in (1, 't', 'tiny'): names = MONTHNAMES1 + elif format in (3, 's', 'small'): names = MONTHNAMES3 + else: names = MONTHNAMES + return names[self.month - 1] + + __monthname1 = lambda self: self.monthname(1) + __monthname3 = lambda self: self.monthname(3) + FORMAT_MAP = {'%Y': '%(y)04i', '%m': '%(m)02i', '%d': '%(d)02i', + '%H': '%(H)02i', '%M': '%(M)02i', '%S': '%(S)02i', + '%1m': __monthname1, '%3m': __monthname3, '%fm': monthname, + '%C': calday, + } + def format(self, format=None): + """Formater la date pour affichage. + + Les champs valides sont %Y, %m, %d qui correspondent à la date de cet + objet, %H, %M, %S qui valent toujours 0, et %1m, %3m, %fm, %C, qui + correspondent respectivement à self.monthname(1), self.monthname(3), + self.monthname(), self.calday(). + """ + if format is None: format = FR_DATEF + y, m, d, H, M, S = self.year, self.month, self.day, 0, 0, 0 + for fr, to in self.FORMAT_MAP.items(): + if callable(to): to = to(self) + format = format.replace(fr, to) + return format % locals() + + def set(self, day=None, month=None, year=None): + kw = {} + for name, value in [('day', day), ('month', month), ('year', year)]: + if value is not None: kw[name] = value + self._d = self._d.replace(**kw) + return self + + def set_weekday(self, weekday=0): + if self.weekday() != weekday: + day = self.day + weekday - self.weekday() + self.set(*_fix_date(day, self.month, self.year)) + return self + + def set_isoweekday(self, isoweekday=1): + if self.isoweekday() != isoweekday: + day = self.day + isoweekday - self.isoweekday() + self.set(*_fix_date(day, self.month, self.year)) + return self + + def __repr__(self): + return '%s(%i, %i, %i)' % (self.__class__.__name__, self.year, self.month, self.day) + def __str__(self): + return '%02i/%02i/%04i' % (self.day, self.month, self.year) + def __unicode__(self): + return u'%02i/%02i/%04i' % (self.day, self.month, self.year) + + def __eq__(self, other): return self._d == self._date(other, False) + def __ne__(self, other): return self._d != self._date(other, False) + def __lt__(self, other): + if other is None: return False + else: return self._d < self._date(other) + def __le__(self, other): + if other is None: return False + else: return self._d <= self._date(other) + def __gt__(self, other): + if other is None: return True + else: return self._d > self._date(other) + def __ge__(self, other): + if other is None: return True + else: return self._d >= self._date(other) + def __cmp__(self, other): + if other is None: return 1 + else: return cmp(self._d, self._date(other)) + def __hash__(self): return hash(self._d) + + def _date(self, d, required=True): + """Retourner l'instance de datetime.date correspondant à l'objet d. + """ + if isinstance(d, pydate): return d + elif isinstance(d, Date): return d._d + elif required: raise ValueError("Expected datetime.date or Date instance, got %s" % repr(d)) + else: return None + + def _delta(self, td): + """Retourner l'instance de datetime.timedelta correspondant à l'objet td + """ + if isinstance(td, timedelta): return td + elif isnum(td): return timedelta(td) + else: raise ValueError("Expected number or datetime.delta instance got %s" % repr(td)) + + def _new(cls, d=None, t=None): + """Constructeur. d est une instance de Date ou datetime.date. t est un + nombre de secondes depuis l'epoch. + """ + if d is not None: + if isinstance(d, pydate): return cls(d.day, d.month, d.year) + elif isinstance(d, Date): return cls(d.day, d.month, d.year) + else: raise ValueError("Expected datetime.date or Date instance, got %s" % repr(d)) + elif t is not None: return cls(t=t) + else: return cls() + _new = classmethod(_new) + + def copy(self): + """Retourner une nouvelle instance, copie de cet objet + """ + return self._new(self._d) + + def replace(self, day=None, month=None, year=None): + """Retourner une nouvelle instance avec les champs spécifiés modifiés. + """ + kw = {} + for name, value in [('day', day), ('month', month), ('year', year)]: + if value is not None: kw[name] = value + return self._new(self._d.replace(**kw)) + + def __add__(self, other): return self._new(self._d + self._delta(other)) + __radd__ = __add__ + def add(self, days=1): return self + days + + def __sub__(self, other): return self._new(self._d - self._delta(other)) + __rsub__ = __sub__ + def sub(self, days=1): return self - days + + def diff(self, other): + """Retourner le nombre de jours de différences entre cette date et other + """ + delta = self._d - self._date(other) + return delta.days + + def __fix_weekday(self, date): + """Si date est après jeudi, retourner le début de la semaine + suivante, sinon retourner le début de la semaine courante. + """ + date = date.copy() + if date.weekday() > 3: + date = date.set_weekday(0) + date += 7 + else: + date.set_weekday(0) + return date + + def get_monthweeks(self, complete=True, only_debut=None): + """Retourner une liste de dates (debut, fin) correspondant aux débuts + et aux fins des semaine du mois de cet objet. + + Si only_debut==True, ne retourner que la liste de valeurs debut au lieu + des tuples (debut, fin). Par défaut only_debut==complete + + Si complete==True, on ne retourne que des semaines complètes: les dates + au début et à la fin du mois sont corrigées pour inclure les jours du + mois précédent et du mois suivant s'il y a au moins 4 jours dans le mois + courant. + + Sinon, les semaines du début et de la fin du mois peuvent être tronquées + et ne contiennent que les jours du mois. + """ + if only_debut is None: only_debut = complete + + first = self.copy().set(1) + monthdays = first.monthdays + last = first + monthdays + weeks = [] + if complete: + first = self.__fix_weekday(first) + last = self.__fix_weekday(last) + debut = first + while debut < last: + fin = debut + 6 + if only_debut: weeks.append(debut) + else: weeks.append((debut, fin)) + debut = fin + 1 + else: + last -= 1 + debut = first + while debut <= last: + fin = debut.copy().set_weekday(6) + if fin > last: fin = last + if only_debut: weeks.append(debut) + else: weeks.append((debut, fin)) + debut = fin + 1 + return weeks + +def isdate(d): + """Tester si d est une instance de Date + """ + return isinstance(d, Date) +def isanydate(d): + """Tester si d est une instance de Date ou datetime.date + """ + return isinstance(d, Date) or isinstance(d, pydate) + +RE_DATE_FR = re.compile(r'(\d+)(?:/(\d+)(?:/(\d+))?)?$') +RE_DATE_ISO = re.compile(r'(\d+)-(\d+)-(\d+)$') +def parse_date(s): + """Parser une chaine et retourner une instance de Date + """ + mof = RE_DATE_FR.match(s) + moi = RE_DATE_ISO.match(s) + if mof is not None: + year = mof.group(3) + month = mof.group(2) + day = mof.group(1) + elif moi is not None: + year = moi.group(1) + month = moi.group(2) + day = moi.group(3) + else: + raise ValueError("Invalid date format: %s" % _s(s)) + if year is not None: year = _fix_year(int(year)) + if month is not None: month = int(month) + if day is not None: day = int(day) + return Date(day, month, year) + +def ensure_date(d): + """Retourner une instance de Date, ou None si d==None. + + d peut être une intance de datetime.date, Date ou une chaine. + """ + if d is None: return None + elif isinstance(d, Date): return d + elif isinstance(d, pydate): return Date._new(d) + if not isstr(d): d = _s(d) + return parse_date(d) + +def _tzname(): + tz = time_mod.timezone + if tz > 0: s = "-" + else: s = "+" + tz = abs(tz) / 60 + h = tz / 60 + m = tz % 60 + return "%s%02i%02i" % (s, h, m) + +def rfc2822(time=None, gmt=True): + """Retourner la date au format rfc 2822. + + time est une date au format de time.time() + """ + if time is None: time = time_mod.time() + if gmt: + time = gmtime(time) + tzname = "+0000" + else: + time = localtime(time) + tzname = _tzname() + return "%s %s" % (asctime(time), tzname) + +class _DateSpecConstants: + """Constantes utilisées par les classes DateSpec et ses filles + """ + + # Contrainte + C = r'(?:!(w|n)(\d+))' + C_COUNT = 2 # nombre de groupes pour l'expression régulière C + C_OP = 0 # numéro relatif du groupe pour la valeur OP + C_WD = 1 # numéro relatif du groupe pour la valeur WEEKDAY + + # Spécification + I = r'(\d+)' + I_COUNT = 1 # nombre de groupes pour l'expression régulière I + I_VALUE = 0 # numéro relatif du groupe pour la valeur VALUE + + R = r'(?:(\d+)(?:\s*-\s*(\d+))?)' # Range + R_COUNT = 2 # nombre de groupes pour l'expression régulière R + R_FROM = 0 # numéro relatif du groupe pour la valeur FROM + R_TO = 1 # numéro relatif du groupe pour la valeur TO + + AOR = r'(?:(\*)|%s)' % R # AnyOrRange + AOR_COUNT = 1 + R_COUNT # nombre de groupes pour l'expression régulière AOR + AOR_R_POS = 1 # position du premier groupe de l'expression R dans AOR + AOR_ANY = 0 + AOR_FROM = AOR_R_POS + R_FROM # numéro relatif du groupe pour la valeur FROM + AOR_TO = AOR_R_POS + R_TO # numéro relatif du groupe pour la valeur TO + + S = r'(?:\+%s|w%s|%s)(?:\s*/\s*%s(?:\s*/\s*%s)?)?' % (I, R, AOR, AOR, AOR) + S_COUNT = I_COUNT + R_COUNT + 3 * AOR_COUNT # nombre de groupes pour l'expression régulière S + S_I_POS = 0 # position du premier groupe de l'expression I dans S + S_R_POS = S_I_POS + I_COUNT # position du premier groupe de l'expression R dans S + S_DAOR_POS = S_R_POS + R_COUNT # position du premier groupe de l'expression DAOR dans S + S_MAOR_POS = S_DAOR_POS + AOR_COUNT # position du premier groupe de l'expression DAOR dans S + S_YAOR_POS = S_MAOR_POS + AOR_COUNT # position du premier groupe de l'expression DAOR dans S + S_OFFSET = S_I_POS + I_VALUE # numéro relatif du groupe pour la valeur OFFSET + S_WD_FROM = S_R_POS + R_FROM # numéro relatif du groupe pour la valeur FROM de WD + S_WD_TO = S_R_POS + R_TO # numéro relatif du groupe pour la valeur TO de WD + S_D_ANY = S_DAOR_POS + AOR_ANY # numéro relatif du groupe pour la valeur ANY de D + S_D_FROM = S_DAOR_POS + AOR_FROM # numéro relatif du groupe pour la valeur FROM de D + S_D_TO = S_DAOR_POS + AOR_TO # numéro relatif du groupe pour la valeur TO de D + S_M_ANY = S_MAOR_POS + AOR_ANY # numéro relatif du groupe pour la valeur ANY de M + S_M_FROM = S_MAOR_POS + AOR_FROM # numéro relatif du groupe pour la valeur FROM de M + S_M_TO = S_MAOR_POS + AOR_TO # numéro relatif du groupe pour la valeur TO de M + S_Y_ANY = S_YAOR_POS + AOR_ANY # numéro relatif du groupe pour la valeur ANY de Y + S_Y_FROM = S_YAOR_POS + AOR_FROM # numéro relatif du groupe pour la valeur FROM de Y + S_Y_TO = S_YAOR_POS + AOR_TO # numéro relatif du groupe pour la valeur TO de Y + + RE_SPEC = re.compile(r'(?:(?:%s)|(?:%s))$' % (C, S)) + # offsets des positions des groupes dans l'expression RE_SPEC + SPEC_C_POS = 0 + SPEC_S_POS = SPEC_C_POS + C_COUNT + # position des groupes dans l'expression RE_SPEC + SPEC_C_OFF = 1 + SPEC_C_POS + CONS_OP = SPEC_C_OFF + C_OP + CONS_WD = SPEC_C_OFF + C_WD + SPEC_S_OFF = 1 + SPEC_S_POS + SPEC_OFFSET = SPEC_S_OFF + S_OFFSET + SPEC_WD_FROM = SPEC_S_OFF + S_WD_FROM + SPEC_WD_TO = SPEC_S_OFF + S_WD_TO + SPEC_D_ANY = SPEC_S_OFF + S_D_ANY + SPEC_D_FROM = SPEC_S_OFF + S_D_FROM + SPEC_D_TO = SPEC_S_OFF + S_D_TO + SPEC_M_ANY = SPEC_S_OFF + S_M_ANY + SPEC_M_FROM = SPEC_S_OFF + S_M_FROM + SPEC_M_TO = SPEC_S_OFF + S_M_TO + SPEC_Y_ANY = SPEC_S_OFF + S_Y_ANY + SPEC_Y_FROM = SPEC_S_OFF + S_Y_FROM + SPEC_Y_TO = SPEC_S_OFF + S_Y_TO + + def _range(f, t=None): + f = int(f) + if t is None: t = f + else: t = int(t) + if t < f: t, f = f, t + return (f, t) + _range = staticmethod(_range) + def _isw(vs): return vs == '*' + _isw = staticmethod(_isw) + def _isr(vs): return isseq(vs) + _isr = staticmethod(_isr) + def _matches(cls, vs, v): + if cls._isw(vs): return True + elif cls._isr(vs): return v >= vs[0] and v <= vs[1] + else: raise ValueError("Invalid format: %s" % _s(vs)) + _matches = classmethod(_matches) + def _tostr(cls, vs): + if cls._isw(vs): + return "*" + elif cls._isr(vs): + if vs[0] == vs[1]: return "%i" % vs[0] + else: return "%i-%i" % vs + else: raise ValueError("Invalid format: %s" % _s(vs)) + _tostr = classmethod(_tostr) + def _check_range(cls, name, vs, min, max): + if (min is not None and (vs[0] < min or vs[1] < min)) or \ + (max is not None and (vs[0] > max or vs[1] > max)): + if min is None: min = u"-INF" + else: min = str(min) + if max is None: max = u"+INF" + else: max = str(max) + raise ValueError("%s values must be in the [%s, %s] range, got %s" % (name, min, max, cls._tostr(vs))) + _check_range = classmethod(_check_range) + def _check_value(cls, name, v, min, max): + if (min is not None and v < min) or (max is not None and v > max): + if min is None: min = u"-INF" + else: min = str(min) + if max is None: max = u"+INF" + else: max = str(max) + raise ValueError("%s value must be in the [%s, %s] range, got %i" % (name, min, max, v)) + _check_value = classmethod(_check_value) + +class DateSpec(_DateSpecConstants): + """Une spécification de dates de la forme D[/M[/Y]], ou une spécification + de contrainte de date de la forme !W. + + - D peut prendre l'une des formes suivantes: + - soit des jours du moins sous la forme *, DAY ou FROM-TO. + - soit des jours de la semaine sous la forme "w"WEEKDAY ou "w"FROM-TO + avec 1=Lundi, ..., 7=Dimanche + - soit une expression relative de la forme "+"DAYS, qui représente + DAYS jours après une date de référence. + - M représente des mois sous la forme *, MONTH ou FROM-TO. + - Y représente des années sous la forme *, YEAR ou FROM-TO. + - W représente des jours de la semaine sous la forme "w"WEEKDAY ou + "n"WEEKDAY avec 1=Lundi, ..., 7=Dimanche + + Exemples: + + w1-5 + Les jours de la semaine + 15/1-6 + Les 15 des mois de janvier à juin + */1 + N'importe quel jour du mois de janvier + !w4 + Spécifier que le jour DOIT être un Jeudi. + !n4 + Spécifier que le jour DOIT être le Jeudi *suivant* la date de référence + """ + + class Strategy(_DateSpecConstants): + def matches(self, date): + u"""Tester si la date correspond à cette spécification de date + """ + raise NotImplementedError + + def fix(self, date, now=None, refdate=None): + u"""Corriger date, refdate étant la date de référence + """ + raise NotImplementedError + + def is_obsolete(self, now=None): + u"""Tester si cette spécification de date est obsolète, c'est à + dire si elle désigne une date passée. + """ + raise NotImplementedError + + class ConstraintStrategy(Strategy): + """Une contrainte de date: + + "!wWEEKDAY" signifie que le jour DOIT être celui spécifié, en restant + dans la semaine en cours. + + "!nWEEKDAY" signifie que le jour DOIT être celui spécifié, mais en + prenant toujours une date future. Il est alors possible de passer sur + la semaine suivante pour arriver au bon jour. + """ + _op = None # op: w ou n + _ws = None # weekdays + + def __init__(self, mo): + self._op = mo.group(self.CONS_OP) + ws = mo.group(self.CONS_WD) + if ws is not None: self._ws = self._range(ws) + if self._ws is not None: + self._check_range("WEEKDAYS", self._ws, 0, 7) + + def __str__(self): + s = "!" + if self._ws is not None: + s += self._op + s += self._tostr(self._ws) + return s + + def matches(self, date): + return True + + def fix(self, date, now=None, refdate=None): + date = ensure_date(date) + expected_wd = self._ws[0] + actual_wd = date.isoweekday() + if expected_wd != actual_wd: + date += expected_wd - actual_wd + if self._op == 'n' and actual_wd > expected_wd: + date += 7 + return date + + def is_obsolete(self, now=None): + return False + + class DateStrategy(Strategy): + """Une spécification de date + """ + _offset = None # offset + _ws = None # weekdays + _ds = None # days + _ms = None # months + _ys = None # years + + def __init__(self, mo): + # offset + o = mo.group(self.SPEC_OFFSET) + if o is None: pass + else: self._offset = self._range(o)[0] + if self._offset is not None: + self._check_value("OFFSET", self._offset, 1, None) + # weekdays + wf, wt = mo.group(self.SPEC_WD_FROM), mo.group(self.SPEC_WD_TO) + if wf is None and wt is None: pass + elif wt is not None: self._ws = self._range(wf, wt) + else: self._ws = self._range(wf) + if self._ws is not None: + self._check_range("WEEKDAYS", self._ws, 0, 7) + # days + dw, df, dt = mo.group(self.SPEC_D_ANY), mo.group(self.SPEC_D_FROM), mo.group(self.SPEC_D_TO) + if dw is None and df is None and dt is None: pass + elif dw is not None: self._ds = '*' + elif dt is not None: self._ds = self._range(df, dt) + else: self._ds = self._range(df) + # months + mw, mf, mt = mo.group(self.SPEC_M_ANY), mo.group(self.SPEC_M_FROM), mo.group(self.SPEC_M_TO) + if mw is None and mf is None and mt is None: self._ms = '*' + elif mw is not None: self._ms = '*' + elif mt is not None: self._ms = self._range(mf, mt) + else: self._ms = self._range(mf) + # years + yw, yf, yt = mo.group(self.SPEC_Y_ANY), mo.group(self.SPEC_Y_FROM), mo.group(self.SPEC_Y_TO) + if yw is None and yf is None and yt is None: self._ys = '*' + elif yw is not None: self._ys = '*' + elif yt is not None: self._ys = self._range(yf, yt) + else: self._ys = self._range(yf) + if self._isr(self._ys): + self._ys = map(_fix_year, self._ys) + + def __str__(self): + s = "" + if self._offset is not None: + s += "+%i" % self._offset + if self._ws is not None: + s += "w" + s += self._tostr(self._ws) + elif self._ds is not None: + s += self._tostr(self._ds) + s += "/" + s += self._tostr(self._ms) + s += "/" + s += self._tostr(self._ys) + return s + + def fill_ranges(self, yrs = None, mrs = None, drs = None, wrs = None): + if yrs is None: yrs = [] + yrs.append(self._ys) + if mrs is None: mrs = [] + mrs.append(self._ms) + if self._ws is not None: + if wrs is None: wrs = [] + wrs.append(self._ws) + elif self._ds is not None: + if drs is None: drs = [] + drs.append(self._ds) + return yrs, mrs, drs, wrs + + def matches(self, date): + date = ensure_date(date) + # tester l'année + if not self._matches(self._ys, date.year): return False + # tester le mois + if not self._matches(self._ms, date.month): return False + # tester weekday ou day + if self._ws is not None: + if not self._matches(self._ws, date.isoweekday()): return False + elif self._ds is not None: + if not self._matches(self._ds, date.day): return False + return True + + def fix(self, date, now=None, refdate=None): + if self._offset is not None: + if now is None: now = Date() + if refdate is None: refdate = now + date = refdate + self._offset + return date + + def is_obsolete(self, now=None): + if self._offset is not None: return False + elif self._ws is not None: return False + elif self._isw(self._ds): return False + elif self._isw(self._ms): return False + elif self._isw(self._ys): return False + if now is None: now = Date() + y = now.year; ys = self._ys + if y > ys[0] and y > ys[1]: return True + elif y < ys[0] and y < ys[1]: return False + m = now.month; ms = self._ms + if m > ms[0] and m > ms[1]: return True + elif m < ms[0] and m < ms[1]: return False + d = now.day; ds = self._ds + if d > ds[0] and d > ds[1]: return True + return False + + _strategy = None + strategy = property(lambda self: self._strategy) + + def is_constraint_spec(self): + """Retourner True s'il s'agit d'une spécification de contrainte de date + """ + return isinstance(self._strategy, self.ConstraintStrategy) + def is_date_spec(self): + """Retourner True s'il s'agit d'une spécification de date + """ + return isinstance(self._strategy, self.DateStrategy) + + def __init__(self, spec): + mo = self.RE_SPEC.match(spec) + if mo is None: + raise ValueError("Invalid DateSpec format: %s" % _s(spec)) + + if mo.group(self.CONS_WD) is None: strategy = self.DateStrategy(mo) + else: strategy = self.ConstraintStrategy(mo) + self._strategy = strategy + + def __str__(self): + return self._strategy.__str__() + + def __repr__(self): + return "%s(\"%s\")" % (self.__class__.__name__, self) + + def matches(self, date): + return self._strategy.matches(date) + + def fix(self, date, now=None, refdate=None): + return self._strategy.fix(date, now, refdate) + + def matches_fix(self, date, now=None, refdate=None): + if self.matches(date): return True, self.fix(date, now, refdate) + else: return False, date + + def is_obsolete(self): + return self._strategy.is_obsolete() + +class DateSpecs: + """Une suite de spécifications de date, séparées par des virgules. + + Attention! l'ordre est important, car les calculs et l'évaluation des + contraintes se fait dans l'ordre des spécifications. + """ + RE_COMMA = re.compile(r'\s*,\s*') + + _specs = None + def __constraint_specs(self): + return [spec for spec in self._specs if spec.is_constraint_spec()] + def __date_specs(self): + return [spec for spec in self._specs if spec.is_date_spec()] + + def __init__(self, specs): + specs = _s(specs).strip() + self._specs = [DateSpec(spec) for spec in self.RE_COMMA.split(specs)] + + def __str__(self): + return ",".join([str(spec) for spec in self._specs]) + + def __repr__(self): + return "%s(\"%s\")" % (self.__class__.__name__, self) + + def matches(self, date): + for spec in self._specs: + if spec.matches(date): return True + return False + + def matches_fix(self, date, now=None, refdate=None): + if now is None: now = Date() + if refdate is None: refdate = now + for spec in self.__date_specs(): + if spec.matches(date): + for spec in self._specs: + date = spec.fix(date, now, refdate) + return True, date + return False, date + + _now = None + _refdate = None + _candidates = None + + def _reset_candidates(self): + self._now = None + self._refdate = None + self._candidates = None + + def _get_candidates(self, now=None, refdate=None): + if now is None: now = Date() + if refdate is None: refdate = now + if self._candidates is not None and \ + now == self._now and refdate == self._refdate: + return self._candidates + + isw = DateSpec._isw + # Enumérer les candidats de weekdays, days, months, years + yrs = None + mrs = None + drs = None + wrs = None + for spec in self.__date_specs(): + yrs, mrs, drs, wrs = spec.strategy.fill_ranges(yrs, mrs, drs, wrs) + # Calculer les dates candidates + # ...years + candidates = {} + if yrs is None: yrs = ['*'] + for ys in yrs: + if ys == '*': + candidates[now.year] = {} + candidates[now.year + 1] = {} + else: + for y in range(ys[0], ys[1] + 1): + candidates[y] = {} + years = candidates.keys() + # ...months + for year in years: + if mrs is None: mrs = ['*'] + for ms in mrs: + if ms == '*': + candidates[year][now.month] = {} + candidates[year][now.month + 1] = {} + else: + for m in range(ms[0], ms[1] + 1): + candidates[year][m] = {} + # ...weekdays or days + for year in years: + for month in candidates[year].keys(): + monthdays = range(1, _monthdays(year, month) + 1) + #candidates[year][month]['ws'] = None + candidates[year][month]['ds'] = None + if wrs is not None: + # si on précise des jours de semaine, + # inclure tous les jours du mois + #ws = [] + #for wr in wrs: + # ws.extend(range(wr[0], wr[1] + 1)) + #candidates[year][month]['ws'] = ws + candidates[year][month]['ds'] = monthdays + elif drs is not None: + ds = [] + for dr in drs: + if isw(dr): ds.extend(monthdays) + else: ds.extend(range(dr[0], dr[1] + 1)) + candidates[year][month]['ds'] = ds + else: + # ni weekdays, ni days, prendre tous les jours du mois + # à configurer ci-dessous quand on saura quel mois prendre + candidates[year][month]['ds'] = monthdays + # fin + self._now = now + self._refdate = refdate + self._candidates = candidates + return candidates + + def get_next_date(self, now=None, refdate=None): + if now is None: now = Date() + if refdate is None: refdate = now + candidates = self._get_candidates(now, refdate) + for year in [year for year in sorted(candidates.keys()) + if year >= now.year]: + for month in [month for month in sorted(candidates[year].keys()) + if Date(0, month + 1, year) >= now]: + days = [day for day in candidates[year][month]['ds'] + if Date(day, month, year) > now] + #weekdays = candidates[year][month]['ws'] + for day in days: + next = Date(day, month, year) + matches, next = self.matches_fix(next, now, refdate) + if matches: return next + return None + + def remove_obsoletes(self): + specs = [spec for spec in self._specs if not spec.is_obsolete()] + if len(specs) != len(self._specs): + self._specs = specs + self._reset_candidates() + return True + else: + return False diff --git a/pyulib/src/ulib/base/dates.pyc b/pyulib/src/ulib/base/dates.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95f8a6f487f70356f67391bc14a9f2357220497b GIT binary patch literal 41075 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/editor.py b/pyulib/src/ulib/base/editor.py new file mode 100644 index 0000000..f87abc4 --- /dev/null +++ b/pyulib/src/ulib/base/editor.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Des fonctions pour éditer des fichiers. +""" + +__all__ = ('edit_file', 'edit_template') + +import os, sys + +from base import isseq +from env import get_editor, get_editor_options, get_editor_setrow +from uio import EditorIO, _s +from lines import Lines +from args import split_args +from tmpfiles import mktemp +from paths import in_PATH +from procs import spawn + +# options, setrow, setcol, colplus +EDITOR_CAPS = {'emacs': ('', '+', ':', 1), + 'xemacs': ('', '+', ':', 1), + 'gvim': ('-f', '+', '', 0), + 'vim': ('-f', '+', '', 0), + 'vi': ('', '+', '', 0), + } +def get_default_editors(): + """Retourner une liste d'éditeurs par défaut pour la plateforme en cours + """ + if sys.platform.startswith('linux'): + return ('emacs', 'xemacs', 'gvim', 'vim', 'vi') + else: + return ('xemacs', 'emacs', 'gvim', 'vim', 'vi') + +def get_editor_caps(): + """Obtenir les caractéristiques de l'éditeur configuré. + + @return: (editor, options, setrow, setcol, colplus) + """ + options = None + setrow = None + setcol = '' + colplus = 0 + + editor = get_editor() + if editor is None: + for editor in get_default_editors(): + if in_PATH(editor): break + else: + raise OSError("Unable to find a default editor. Please set UTOOLS_EDITOR.") + + if EDITOR_CAPS.has_key(editor): + options, setrow, setcol, colplus = EDITOR_CAPS[editor] + + if options is None and setrow is None: + options = split_args(get_editor_options()) + setrow = get_editor_setrow() + if options is None and setrow is None and EDITOR_CAPS.has_key(editor): + options, setrow, setcol, colplus = EDITOR_CAPS[editor] + + return editor, options, setrow or '', setcol or '', int(colplus) + +def edit_file(file, row=None, col=None): + """Lancer un éditeur pour éditer le fichier file. + + @return: le status d'exécution de l'éditeur. + """ + editor, options, setrow, setcol, colplus = get_editor_caps() + + cmd = [editor] + if options: + if isseq(options): cmd.extend(options) + else: cmd.append(options) + if setrow and row is not None: + row = int(row) + opt = '%s%i' % (setrow, row) + if setcol and col is not None: + col = int(col) + opt += '%s%i' % (setcol, col + colplus) + cmd.append(opt) + cmd.append(file) + return spawn(*cmd) + +def edit_template(template=None, strip_prefix=None, row=None, col=None, lines=None): + """Obtenir une valeur éditée dans un éditeur. + + Un fichier temporaire vide est initialisé avec le contenu de template, + puis le fichier est proposé à l'édition. + + A la sortie, toutes les lignes commençant par strip_prefix sont supprimée, + et une instance de Lines avec les lignes du fichier est retourné. + + @return: lines + @rtype: Lines + """ + if lines is None: + uio = EditorIO() + lines = Lines(uio=uio) + else: + uio = lines.uio + if uio is None: + uio = EditorIO() + lines.uio = uio + + ## préparer le fichier + tmpf, tmpfile = mktemp('utools') + try: + if template is not None: + template = uio.s(template) + try: tmpf.write(template) + finally: tmpf.close() + else: + tmpf.close() + + ## l'éditer + edit_file(tmpfile, row, col) + + ## traiter le résultat + lines.readlines(tmpfile) + + # enlever les préfixes + if strip_prefix is not None: + lines.filter(lambda l: not l.startswith(strip_prefix)) + + # supprimer les lignes vides au début et à la fin + while lines and not lines[0].strip(): del lines[0] + while lines and not lines[-1].strip(): del lines[-1] + + return lines + finally: + os.remove(tmpfile) diff --git a/pyulib/src/ulib/base/editor.pyc b/pyulib/src/ulib/base/editor.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ab7b1ed4c48a8cdd90d36950a8b384735a0506d GIT binary patch literal 4301 zcmb_f&2AgX5w0dhilit@vSnG4W$&zaouxNg6WAbjU~D!vyauv>AqgWpS`ZGH5vM7& zIh+|y5B0N=E|HPs5abzh3-lHN^aOc`oN~)Cm*lHzl9sZIMGg`*HT_f7UETH7&;0#g z)3slDf7|HL@NW+9r+Dl=MHJ#+QIDvF#%*e;(MW+>g;Bdmt)jFis5e1TiCQI6ELoyn znW9N*O;S{$R)wM}wW@BeOuZUKQ`DNGs7|fA8=a)yG)32_b7b#qn#uA^I|Ci_;&Ut&(b%UVG^bUu_+KM(CAL6defAF`KSJBj=$=h8M z>3n+B%XL4Ef?PLnp_G@H-&(!bC~~(3vZM1h%OzQU{ZhA`q`66Ao3l~gbG5C`v*e&r z-~}omFGTq3^_)lA_WedLO{|Aq-pHmAvHlE|L9tbwU zvIf9XbgC#}7*%Lfg;m-NCvn$uhL_;q3g zK}^EOWI%~YnlN!VK_`w1`cGh+0-Y2Y3a3OT6LdVmJZ~$M{D#zFlVD&>eD&)^o`lsg zKQ!gW-p@$k*#aoSB9Hz~u{)UZh-sM=`jA+9Bio)R!iLyw@6V)SMe0~o;msbP1C89y zE0CLHPr%Mt77D`^Sv^IUr5<0D`c!uNKX#Wja8e>{nN9$dijE7T77TbSr|Fh}P`*HW z1hT%J>(~Hz+IpQJ3p)AvSq7Y%y#Yo-fOZU5U@`8Rok4bf_D~QhUfAilnzW#Ev{o<>yU#VuVPapXl6nzc@KHrVR0TbtPQNlM0ag0QFFo`8-;#k5lJe~|Yt*_WYX0Y{z=-LTV z`w?sNBM>T;mHY}-HLYq?R?}21)C;rfZedx?VC+-1Tri|;aj!|}sY#leoNA_6zWzLRu|#U4XWa1U*;Iot{E zcVR#rO~E8k_fG`i^99&G$jcz^NL=0 z$YcB=lop+404{g|yFZS1L4shNYxD=Au7v;JyTuuqhxb|C5(qqU|>_QX7_Q`qbSc+$bJx^_Hd{_Khs_q z#1`er*bOcq{dGL{0WQ9hEHD`sFh~p9=AMa7ZUR&_=VxB9uRH8B$PobZfSq7ePp(S5 zPY1?cbShpT&5|@>>GQvg@(dj4f&$ODAhL+uMz10_407Zo&Qy`vLBWX4`h6Xr|22rQ z7D_3bXC`f@neLiHh+)(Y(lj%DoYQH6?7??nP^+^4vzk(Sj`SZd9S+7vXHAjhxRB$_ zcRA~!Ak(fsdz$4(seafi;L9_I|E->@^Tz52+9U0*>mN&QY1k zUj%U_E_Wl>p~1Sk;KXsuXLf&;2|G3% zlJ#O=KgdwvN~;qkR!gjq&yGw7pv%bP(AMxV#X;oD>B=sWw7O$%! zqTY-uA{x#VYQ@>&ba7fO7mKt+b&P@*Dh2s2vRrp?wICm3_PZZrQj+knFPG0XfPB+5 zesE&=apPphm6pi-z?9Tv|0{C`J4)qb+OgJQJ3adRCvv;(b`VAFw*Q!wTVO6$iHmxd z+Bnebc;6LdqAlCX;YHvhsgR350g_1bH=mSZ&gRa3*1?OV(;(lquCSACmUOJ-NPmUb zk+Sb&Zb=43gTzt}0o;|=RUkkV{K9?We96u%WmvsbE-a$xT2TwgfWu$0hRn#nTA^C0 IuFTy0FBjj9fB*mh literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/encdetect.py b/pyulib/src/ulib/base/encdetect.py new file mode 100644 index 0000000..af55f5a --- /dev/null +++ b/pyulib/src/ulib/base/encdetect.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Fonctions pour détecter l'encoding d'une chaine ou d'un fichier, et/ou tester +si c'est un fichier binaire. +""" + +__all__ = ('UNRECOGNIZED_ENCODING', 'UNKNOWN_ENCODING', + 'guess_encoding', 'guess_string_encoding', 'guess_stream_encoding', + 'detect_line_encoding', 'guess_line_encoding', + 'FileType', + ) + +from os import path +import re + +from base import isstr, make_prop +from encoding import LATIN1, UTF8, MACROMAN +from env import get_default_encoding + +# Les tableaux suivants contiennents les caractères suivants: +# àâçèéêîïñôû + +ISO_8859_1_CHARS = [ + 0xe0, 0xe2, 0xe7, 0xe8, 0xe9, 0xea, + 0xee, 0xef, 0xf1, 0xf4, 0xfb, +] + +MAC_ROMAN_CHARS = [ + 0x88, 0x89, 0x8d, 0x8f, 0x8e, 0x90, + 0x94, 0x95, 0x96, 0x99, 0x9e, +] + +# la séquence est 0xc3 puis l'un des caractères de ce tableau +UTF_8_CHARS = [ + 0xa0, 0xa2, 0xa7, 0xa8, 0xa9, 0xaa, + 0xae, 0xaf, 0xb1, 0xb4, 0xbb, +] + +UNKNOWN_ENCODING = "Unknown" +UNRECOGNIZED_ENCODING = "Unrecognized" +def guess_string_encoding(ins, unknown=UNKNOWN_ENCODING, unrecognized=UNRECOGNIZED_ENCODING): + ascii = True + i = 0 + max = len(ins) + while i < max: + b = ord(ins[i]) + if b >= 128: ascii = False + if b == 0xc3: + b = ord(ins[i + 1]) + if b in UTF_8_CHARS: return UTF8 + elif b in ISO_8859_1_CHARS: return LATIN1 + elif b in MAC_ROMAN_CHARS: return MACROMAN + elif not ascii: return unrecognized + i = i + 1 + if unknown is None: return get_default_encoding() + else: return unknown + +def guess_stream_encoding(inf, unknown=UNKNOWN_ENCODING, unrecognized=UNRECOGNIZED_ENCODING): + close_inf = False + if isstr(inf): + inf = open(inf, 'rb') + close_inf = True + try: + return guess_string_encoding(inf.read(), unknown, unrecognized) + finally: + if close_inf: inf.close() + +def guess_encoding(ins=None, inf=None, unknown=None, unrecognized=UNRECOGNIZED_ENCODING): + if ins is not None: return guess_string_encoding(ins, unknown, unrecognized) + elif inf is not None: return guess_stream_encoding(inf, unknown, unrecognized) + else: return unknown + +RE_ENCODING = re.compile(r'(?i)\b(?:en)?coding: (\S+)\b') +def detect_line_encoding(lines, examine_lines=10): + nb_lines = len(lines) + if nb_lines < 2 * examine_lines: + examine_lines = nb_lines + + for line in lines[:examine_lines]: + mo = RE_ENCODING.search(line) + if mo is not None: return mo.group(1) + if nb_lines > examine_lines: + for line in lines[-examine_lines:]: + mo = RE_ENCODING.search(line) + if mo is not None: return mo.group(1) + return None + +_UNKNOWN = object() +_UNRECOGNIZED = object() +def guess_line_encoding(lines, unknown=None, unrecognized=UNRECOGNIZED_ENCODING): + for line in lines: + encoding = guess_string_encoding(line, _UNKNOWN, _UNRECOGNIZED) + if encoding is _UNKNOWN: continue + elif encoding is _UNRECOGNIZED: return unrecognized + else: return encoding + if unknown is None: return get_default_encoding() + else: return unknown + +class FileType(object): + """Un objet servant à déterminer le type d'un fichier: + - texte ou binaire + - encoding + + XXX finir cette classe, et intégrer les fonctions de paths + """ + _check_ext, check_ext = make_prop('_check_ext', True)[:2] + _check_content, check_content = make_prop('_check_content', True)[:2] + _file, file = make_prop('_file')[:2] + + def __init__(self, file): + self._file = file + + def is_binary(self): + binary = self._binary + if binary is None and self.check_ext: + binary = self.is_binary_ext(self.file) + if binary is None and self.check_context: + content = self.get_content(self.file) + binary = self.is_binary_content(content) + if binary is not None: + self._binary = binary + return binary + _binary, binary = make_prop('_binary', getter=is_binary)[:2] + + def is_binary_ext(self, file): + _, filename = path.split(file) + _, ext = path.splitext(filename) + if filename == '.DS_Store': return True + else: return ext.lower() in ( + # exécutables et fichiers objets + '.bin', '.com', '.co_', '.exe', '.ex_', '.dll', + '.pyc', '.pyd', '.pyo', '.class', + '.o', '.so', '.so.*', '.lib', '.ovl', + # archives + '.gz', '.bz2', '.tar', '.tgz', '.tbz2', + '.hqx', '.sit', '.zip', '.jar', '.rpm', '.srpm', '.deb', + # multimédia + '.bmp', '.gif', '.png', '.jpeg', '.jpg', '.tif', '.tiff', + '.xbm', '.icns', '.ico', '.avi', '.mov', '.mpg', '.swf', + '.mp3', '.snd', '.ogg', '.dat', + # documents + '.doc', '.ppt', '.xls', '.pdf', + # divers + '.bpt', '.bro', '.eps', '.fm', '.ins', '.mcp', '.objectplant', + '.ofp', '.opn','.pqg', '.prj', '.ps', '.sl', '.strings', '.wordbreak', + ) + + def get_content(self, file): + pass #XXX + + def is_binary_content(self, content): + pass #XXX diff --git a/pyulib/src/ulib/base/encdetect.pyc b/pyulib/src/ulib/base/encdetect.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef3a1661c164bf359ff6fa780c11baace6809dd6 GIT binary patch literal 6608 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/encoding.py b/pyulib/src/ulib/base/encoding.py new file mode 100644 index 0000000..0d1dc7f --- /dev/null +++ b/pyulib/src/ulib/base/encoding.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Gestion de la langue et de l'encoding par défaut. +""" + +__all__ = ('LATIN1', 'LATIN9', 'UTF8', 'MACROMAN', + 'normalize_encoding', 'get_encoding_or_default', + ) + +import os, locale +from locale import setlocale, LC_ALL, getlocale, getdefaultlocale + +# Gestion des formes normalisées des encodings +# note: Ces formes sont déclarées normalisées par rapport à ulib, et non par +# rapport à un quelconque organisme de normalisation. + +LATIN1 = 'iso-8859-1' +LATIN9 = 'iso-8859-15' +UTF8 = 'utf-8' +MACROMAN = 'MacRoman' + +ENCODING_MAP = {'latin-1': LATIN1, + 'latin1': LATIN1, + 'iso-8859-1': LATIN1, + 'iso-88591': LATIN1, + 'iso8859-1': LATIN1, + 'iso88591': LATIN1, + 'latin-9': LATIN9, + 'latin9': LATIN9, + 'iso-8859-15': LATIN9, + 'iso-885915': LATIN9, + 'iso8859-15': LATIN9, + 'iso885915': LATIN9, + 'utf-8': UTF8, + 'utf8': UTF8, + 'utf': UTF8, + } + +def normalize_encoding(encoding): + if encoding is None: return None + lencoding = str(encoding).lower().replace('_', '-') + return ENCODING_MAP.get(lencoding, encoding) + +DEFAULT_LANG = 'fr_FR.UTF-8' +LANG_MAP = {LATIN1: 'fr_FR', + LATIN9: 'fr_FR@euro', + UTF8: 'fr_FR.UTF-8', + } + +def get_lang_for_encoding(encoding): + return LANG_MAP.get(normalize_encoding(encoding), DEFAULT_LANG) + +def __set_locale_noexc(lang): + os.environ['LANG'] = lang + try: + setlocale(LC_ALL, '') + return True + except locale.Error: + return False + +__locale_set = False +def __set_locale(): + global __locale_set + if not __locale_set: + lang = os.environ.get('LANG', '') + if not lang or normalize_encoding(lang) == UTF8: + os.environ['LANG'] = DEFAULT_LANG + try: + setlocale(LC_ALL, '') + except locale.Error: + print "WARNING: La valeur LANG='%s' n'est pas valide ou n'a pas été reconnue par le systeme." % os.environ['LANG'] + langs = (LATIN1, LATIN9, 'C') + if os.environ['LANG'] != DEFAULT_LANG: + print "WARNING: La valeur LANG='%s' sera utilise à la place si possible." % DEFAULT_LANG + if __set_locale_noexc(DEFAULT_LANG): + langs = None + else: + print "WARNING: La valeur LANG='%s' n'a pas pu etre selectionnee." % DEFAULT_LANG + if langs is not None: + for lang in langs: + if __set_locale_noexc(lang): + print "NOTE: la valeur LANG='%s' a ete selectionnee" % lang + break + else: + print "WARNING: La valeur LANG='%s' n'a pas pu etre utilisee." % lang + + __locale_set = True + +try: from UTOOLS_CONFIG import SET_LOCALE +except ImportError: SET_LOCALE = True +if SET_LOCALE: __set_locale() + +def get_encoding_or_default(encoding=None, default_encoding=UTF8): + """Si encoding est None, essayer de déterminer l'encoding par défaut avec + getlocale(), getdefaultlocale() puis default_encoding. + """ + if encoding is None: _, encoding = getlocale() + if encoding is None: _, encoding = getdefaultlocale() + if encoding is None: encoding = default_encoding + return normalize_encoding(encoding) diff --git a/pyulib/src/ulib/base/encoding.pyc b/pyulib/src/ulib/base/encoding.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7a74ec77d1e6d6765c95c792f307c46104278b2 GIT binary patch literal 3523 zcmb_e&2k$>5blvIS&}U~j(=jukZi(_1r=LmD7)fRfC$G9sdD7h%3w+bY;CmTWS#Y{ zva<@9f)gBg46Yn_0}fQ>%z;PX6?g&ox<`^@Amn1%+tWR>-8=nF|IF`yRx6)4zuxaq z|2Ky579L|1QGkC%kBQn~u2NeKW^&Zd4fs5@^TL{D%+q>%oPq`V9{yA*xJc~@dW5Yn@&?v}R|wYEF%8xA_9O+b%A4mXxFpOJ z>GR}IQ*c?t89JU3w;g&sOVJ#)=Sc0x3pAoH&|!s=Dk=Q*JTJsKQlIKMO2$a(8TyV$ z#R`}x{#bB@z6YQe8S7T^y`xNss&}{91cD?j%7NKg=#*`k~H-6JC;ffj;niQC4F^SsBFWEJ`|lr0r)my~fUt zEI_lraL(H4tJ+CZ!DMKXfLN}N zCwv2jts1}Aw1LSV+i6(W-FXPI4?*Sj)^_9Lojs42Ma5mfy0|RlZ2Znyxl2U=wZ>di? z(E5?Xz_X9A1@{;b7$QoH#EFidhG`PJ{AHH|M*?dv?b$T$CoUh$R+=U@%C`N;Xn{;# zX3x$XHm*dThnnKqEqHOFzv`@G)^JE8fK(n&NlmE^%k0t+y*Cj7aJRdtIN6Y9r*cek za=bfD45H0MfOwdrWC~M7vMLNZSJ&ey0#L}5KJfeTF95wvVMRdySwIi+Ct3@0cRtF- z2r(9n&_P~uS~dZT3ow6}r(_wWpg<7#-6}IE2T4T=1Zu#O-@subvd)`8Ilze?mMPgF z_0>9;hufdOP2a-JYTt!q0y?8Cz~=oYe?ThrB?$XZ=9QBEkxi11BF}w;V}z5w>0SnP z9T2k*vfprJ-J9p8D1g5FCV4xsmz-Y`o}yrfO$bbZ51VW6F)hdEznp8b?P`^ial zCyC>p##HJ=+A+r_(?@zms@JCZ@MT{xI`y4i7Dl1b&a)q#2*$aVIVNBd*;mn02o*8P+A2qu70w$uJ+?*I9gn2_AB}xVfBe?lhCvK%|ztOW&PmkOJ5BI)Mk83Kr=t! zQ?1Y$)MSTao4e{YaK=B@se=d)5Z5|A3S)@T((qjlZ!F;&lsW!W-KjYE4KF*jx2~~p z>aJ6J%jxz)1DkSp8n%KJQME109%~8vayd6`-(+~CJ!KqA|5SIUFnQJo?sCol0*{#h zp^++0qiOR~QE~$@9HRDOdBSd(5zO+%Qu_ShVrl;SVrl;GVz~%IsH14{9x^uYy2tOl zD=o|O{3!A~n?RBsC&_Yx8x;G=B1t6(^5+Cln`5uAc!>#v=f270Z6;hCP*~ZK-QAs* zx4GNgzI(@A=4sy1-J>pAp)^EEKzn8k*vTmfcSd&z_Ufj+alOypm?p?L?vus*yt-N} kD*4M*)aVQUD*1AusQ3+V`EseK%8PlLQ4=(V3?0k;4Qq$zQUCw| literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/env.py b/pyulib/src/ulib/base/env.py new file mode 100644 index 0000000..afd66de --- /dev/null +++ b/pyulib/src/ulib/base/env.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Accès aux paramètres configurables de ulib/utools dans l'environnement. +""" + +__all__ = ('get_default_encoding', 'get_input_encoding', 'get_output_encoding', + 'get_editor', 'get_editor_options', 'get_editor_setrow', 'get_editor_encoding', + 'get_pager', + ) + +from os import environ + +from encoding import get_encoding_or_default, UTF8 + +try: from ULIB_CONFIG import DEFAULT_INPUT_ENCODING +except ImportError: DEFAULT_INPUT_ENCODING = UTF8 +try: from ULIB_CONFIG import DEFAULT_OUTPUT_ENCODING +except ImportError: DEFAULT_OUTPUT_ENCODING = UTF8 + +def get_default_encoding(encoding=None, default_encoding=DEFAULT_OUTPUT_ENCODING): + """Si encoding est None, essayer de déterminer l'encoding par défaut avec + getlocale(), getdefaultlocale() puis DEFAULT_ENCODING. + """ + return get_encoding_or_default(encoding, default_encoding) + +def get_input_encoding(): + encoding = environ.get('UTOOLS_INPUT_ENCODING', None) + if encoding is None: + encoding = environ.get('UTOOLS_OUTPUT_ENCODING', None) + return get_default_encoding(encoding, DEFAULT_INPUT_ENCODING) + +def get_output_encoding(): + encoding = environ.get('UTOOLS_OUTPUT_ENCODING', None) + return get_default_encoding(encoding, DEFAULT_OUTPUT_ENCODING) + +def get_editor(): + return environ.get('UTOOLS_EDITOR', environ.get('EDITOR', None)) +def get_editor_options(): + return environ.get('UTOOLS_EDITOR_OPTIONS', None) +def get_editor_setrow(): + return environ.get('UTOOLS_EDITOR_SETROW', None) +def get_editor_encoding(): + encoding = environ.get('UTOOLS_EDITOR_ENCODING', None) + if encoding is None: + encoding = environ.get('UTOOLS_INPUT_ENCODING', None) + if encoding is None: + encoding = environ.get('UTOOLS_OUTPUT_ENCODING', None) + return get_default_encoding(encoding, DEFAULT_INPUT_ENCODING) + +def get_pager(): + return environ.get('UTOOLS_PAGER', environ.get('PAGER', None)) +def get_pager_options(): + return environ.get('UTOOLS_PAGER_OPTIONS', None) diff --git a/pyulib/src/ulib/base/env.pyc b/pyulib/src/ulib/base/env.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9f66e87ccb31f59cff6efb05ab5611eea2ee470 GIT binary patch literal 3140 zcmcf@%Wm67a7f9DBwKzeN{bdKdnlryMg$}%&_fV3v1LPmK^iNmDRdziaqG~gxdeBW zKtOUz4*iw$ zPCot{Z~-tu;1)~_jcLM#31I~$6)=cn9WJU6uEAst!Wv9!V9eW^aRV-HKv;)K9gHW@ zr%>e$IB$Yzfq@@K-a14k7(ZnEEzRGAN%PP6n<+mE#hc)7!W$DN8{luj8CzRk3PdE;PKnEvTqWX#%FoQ03lSO_ zckG?sE0SCnRYlr(bo6bywS%j}{?ViH$;chpr{j^^ zw-21dfqk6P>7<Nhw(=f1dVJt3{Ws8XK zBP6{!Pb{BX{=2t|CznBl%kXx=8+%B7_ZHotEblp=HZAW`3Ba~mJwsEoQ@-E^)Px1jmy=UT*_s*xG7eu`mBJN!Ze-?7t zi)KoQQ1;^aEDWCZWHRk(;Hf8h51n&2o-;b?CJsqfgQ`wFvQS~b$GAWP2AFO-7s24X8@b@Z*aI-O@Lx@kOeoRi^-XB9a;qpbL}yM`o2M!^$x z4(NCxZ)xe-I$~2BlC!VUIAn;=a7elxj9sH;bd2{+^aT>%H&I!CGOx|Zn+tsyWf`OV zO%>T7D&5JhS`oVaOB|9ijTF&J5KP8Bz#%`!sZe6Yqe~tn$ud9n4+kTMsTyKZuuJ~Y zo?2+OaEFHed%)5iU*SjtcA*I2wIXEWLXk8ghdQxJ;g?G6I;W$7V-K%IaHv{|Far_1p+-(E+dt{DkEe=d%0L-e#|Q>+d_uF zNwfd2UQjB0jYCo<=k=nf8HD?{=|%@-em{M5+-Hgo(9pGbo?h|$I6fxXmO@qwe4V*g z^e?(2$-A0w7MgUaqDvC-Lp_zGnoNXpPbKsIXQii-<({h8UGAym2YagG%$1%>mU}9C zI28qM;6|MLZalwtU-ww9>xH4~Dyr~8rp1?PwC*Pu38uG=PX>?O1IIoZ9P9T|BL|l; zc7XjP5ecI}TY8H0D@129UDGM;7=<3A5N8w?j2_0TuI`H5@*wSB9@6!4AE%8qz<#1u c*)}_71=_|YtQ*;{xm9b_8r6oW|LP6%4gORhPj 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 aRIQr2YAsqbGmV)=RZ|~Xjk%?Iqw)`EXq}}1 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/functions.py b/pyulib/src/ulib/base/functions.py new file mode 100644 index 0000000..8b3b16e --- /dev/null +++ b/pyulib/src/ulib/base/functions.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Des fonctions pour travailler avec les fonctions. +""" + +__all__ = ('apply_args', 'unpack_results') + +from types import MethodType, FunctionType + +from uio import _s +from base import isseq + +def apply_args(func, *args, **kw): + u"""Appliquer à la fonction func les arguments args, en tenant compte du + nombre de ses arguments et en ignorant les arguments "en trop". + + Par exemple, si f est défini ainsi: + + def f(a, b): + pass + + Alors: + + apply_args(f, 1, 2, 3) + + Est équivalent à: + + apply(f, (1, 2)) + """ + f = func + ac_offset = 0 + if type(f) is MethodType: + f = f.im_func + ac_offset = 1 + if type(f) is not FunctionType: + raise ValueError("func must be a function") + argcount = f.func_code.co_argcount - ac_offset + args = args[:argcount] + return func(*args, **kw) + +def unpack_results(results, *defaults): + u"""results étant une valeur scalaire ou une séquence, retourner une + séquence ayant le même nombre de valeurs de defaults, et en fournissant + les valeurs de defaults si elles ne sont pas présentes dans results. + + >>> unpack_results((1,2), "a", "b", "c") + (1, 2, 'c') + >>> unpack_results("x", "a", "b", "c") + ('x', 'b', 'c') + """ + if not isseq(results): results = (results,) + expected = len(defaults) + got = len(results) + head = tuple(results[:expected]) + if got < expected: tail = tuple(defaults[-(expected - got):]) + else: tail = () + return head + tail diff --git a/pyulib/src/ulib/base/functions.pyc b/pyulib/src/ulib/base/functions.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e20d3be66989f32f4c92a48ceb361199fee04c6 GIT binary patch literal 1994 zcmb_d&2A$_5U%lGY;S@Bi`ZQWiIy*MkQIyU4WWodS~vkKFWQ5J7`12G$;30$lj-i= zSV}IynP=b{o&qUPz^m{A@KsH+CMd@pd&*VSRn^_q-`D=@@15Z*_2-L(Zk`@~zr^c) zf=Ka=Xhk%KGN5@Ro(`>Al(%W#ro2P*4n-W-rB#>m9?g4=-J{hO<$ap>VQwuzs`7QL>tpBNgBi+7C#$$NF67>gTX0F(x5l5?^oMn2{5E`F zhcfY*E!i=SIEW)-*XUj7nRDhW;UV5!2k-ZI-30{R%Qi3OvO^wbn|_JNM|9dJ`;a2o zSxX`yJ5)v#ZKB$AIv~3XSBFwb{5g^k&gxRuql*qbzUFCBm-xXHkS+R+uzg-h zKu_rzHlCdYQ>*LWRj$?2Hd|6l>`mpyDKsS%ru3q@i87_~rqrcZi7hH`R2qf~Pic!q z4a+EZC(C$F$&O1~bJE?CBd%84YIGnqM7+?oGUukKax+mbQ%hx>SLyZDGAlEsv(jas zZ=!kX)GXCJ9LPT&bXw*e;jO zc;9bgnsn$iX5qeqn4Z|eOiz;>ZJ)le)wHl_z!6T%;H}Nwv|5KeTTES@Ohs8}XQsEC za|hLW#6!hFM#uOS%h#FVIZKn5%tM4^TTfN{hl zmo4T?69p=Pd<^myDIV)EqhOp8*(V?h1_l1`K7Q~CM|7#&!u=o`=mMTRdMQ*7KcY)F z+{eLthHWT~;^Pmsa*0Me0CgM0?HC)TOn}zfcpK^xY(l(X=k1`<>jwU+xc;Ls|Dj7$ z)-iJcWX(4YSSGAlay8r`$dqbKo_oTXB_`)VtkPL57r<9leSPJCSGZGMIxJ3ZvxA|O zIyySSBJW&tK=0xHM2++aV!pYL+rVqFdoZJV^ y9l$*bnVrd1!Gf74?GuL0cx{^ULR^sgd literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/htmlentities.py b/pyulib/src/ulib/base/htmlentities.py new file mode 100755 index 0000000..98cc6fb --- /dev/null +++ b/pyulib/src/ulib/base/htmlentities.py @@ -0,0 +1,360 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Des fonctions pour gérer les entités html. +""" + +__all__ = ('quote_entity', 'unquote_entity', + 'quote_html', 'unquote_html', + 'quote_but_html', 'unquote_but_html', + 'quote_attr', 'unquote_attr', + ) + +from types import UnicodeType + +from uio import _u, _s + +name2codepoint = { + 'AElig': 0x00c6, # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 + 'Aacute': 0x00c1, # latin capital letter A with acute, U+00C1 ISOlat1 + 'Acirc': 0x00c2, # latin capital letter A with circumflex, U+00C2 ISOlat1 + 'Agrave': 0x00c0, # latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1 + 'Alpha': 0x0391, # greek capital letter alpha, U+0391 + 'Aring': 0x00c5, # latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1 + 'Atilde': 0x00c3, # latin capital letter A with tilde, U+00C3 ISOlat1 + 'Auml': 0x00c4, # latin capital letter A with diaeresis, U+00C4 ISOlat1 + 'Beta': 0x0392, # greek capital letter beta, U+0392 + 'Ccedil': 0x00c7, # latin capital letter C with cedilla, U+00C7 ISOlat1 + 'Chi': 0x03a7, # greek capital letter chi, U+03A7 + 'Dagger': 0x2021, # double dagger, U+2021 ISOpub + 'Delta': 0x0394, # greek capital letter delta, U+0394 ISOgrk3 + 'ETH': 0x00d0, # latin capital letter ETH, U+00D0 ISOlat1 + 'Eacute': 0x00c9, # latin capital letter E with acute, U+00C9 ISOlat1 + 'Ecirc': 0x00ca, # latin capital letter E with circumflex, U+00CA ISOlat1 + 'Egrave': 0x00c8, # latin capital letter E with grave, U+00C8 ISOlat1 + 'Epsilon': 0x0395, # greek capital letter epsilon, U+0395 + 'Eta': 0x0397, # greek capital letter eta, U+0397 + 'Euml': 0x00cb, # latin capital letter E with diaeresis, U+00CB ISOlat1 + 'Gamma': 0x0393, # greek capital letter gamma, U+0393 ISOgrk3 + 'Iacute': 0x00cd, # latin capital letter I with acute, U+00CD ISOlat1 + 'Icirc': 0x00ce, # latin capital letter I with circumflex, U+00CE ISOlat1 + 'Igrave': 0x00cc, # latin capital letter I with grave, U+00CC ISOlat1 + 'Iota': 0x0399, # greek capital letter iota, U+0399 + 'Iuml': 0x00cf, # latin capital letter I with diaeresis, U+00CF ISOlat1 + 'Kappa': 0x039a, # greek capital letter kappa, U+039A + 'Lambda': 0x039b, # greek capital letter lambda, U+039B ISOgrk3 + 'Mu': 0x039c, # greek capital letter mu, U+039C + 'Ntilde': 0x00d1, # latin capital letter N with tilde, U+00D1 ISOlat1 + 'Nu': 0x039d, # greek capital letter nu, U+039D + 'OElig': 0x0152, # latin capital ligature OE, U+0152 ISOlat2 + 'Oacute': 0x00d3, # latin capital letter O with acute, U+00D3 ISOlat1 + 'Ocirc': 0x00d4, # latin capital letter O with circumflex, U+00D4 ISOlat1 + 'Ograve': 0x00d2, # latin capital letter O with grave, U+00D2 ISOlat1 + 'Omega': 0x03a9, # greek capital letter omega, U+03A9 ISOgrk3 + 'Omicron': 0x039f, # greek capital letter omicron, U+039F + 'Oslash': 0x00d8, # latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1 + 'Otilde': 0x00d5, # latin capital letter O with tilde, U+00D5 ISOlat1 + 'Ouml': 0x00d6, # latin capital letter O with diaeresis, U+00D6 ISOlat1 + 'Phi': 0x03a6, # greek capital letter phi, U+03A6 ISOgrk3 + 'Pi': 0x03a0, # greek capital letter pi, U+03A0 ISOgrk3 + 'Prime': 0x2033, # double prime = seconds = inches, U+2033 ISOtech + 'Psi': 0x03a8, # greek capital letter psi, U+03A8 ISOgrk3 + 'Rho': 0x03a1, # greek capital letter rho, U+03A1 + 'Scaron': 0x0160, # latin capital letter S with caron, U+0160 ISOlat2 + 'Sigma': 0x03a3, # greek capital letter sigma, U+03A3 ISOgrk3 + 'THORN': 0x00de, # latin capital letter THORN, U+00DE ISOlat1 + 'Tau': 0x03a4, # greek capital letter tau, U+03A4 + 'Theta': 0x0398, # greek capital letter theta, U+0398 ISOgrk3 + 'Uacute': 0x00da, # latin capital letter U with acute, U+00DA ISOlat1 + 'Ucirc': 0x00db, # latin capital letter U with circumflex, U+00DB ISOlat1 + 'Ugrave': 0x00d9, # latin capital letter U with grave, U+00D9 ISOlat1 + 'Upsilon': 0x03a5, # greek capital letter upsilon, U+03A5 ISOgrk3 + 'Uuml': 0x00dc, # latin capital letter U with diaeresis, U+00DC ISOlat1 + 'Xi': 0x039e, # greek capital letter xi, U+039E ISOgrk3 + 'Yacute': 0x00dd, # latin capital letter Y with acute, U+00DD ISOlat1 + 'Yuml': 0x0178, # latin capital letter Y with diaeresis, U+0178 ISOlat2 + 'Zeta': 0x0396, # greek capital letter zeta, U+0396 + 'aacute': 0x00e1, # latin small letter a with acute, U+00E1 ISOlat1 + 'acirc': 0x00e2, # latin small letter a with circumflex, U+00E2 ISOlat1 + 'acute': 0x00b4, # acute accent = spacing acute, U+00B4 ISOdia + 'aelig': 0x00e6, # latin small letter ae = latin small ligature ae, U+00E6 ISOlat1 + 'agrave': 0x00e0, # latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1 + 'alefsym': 0x2135, # alef symbol = first transfinite cardinal, U+2135 NEW + 'alpha': 0x03b1, # greek small letter alpha, U+03B1 ISOgrk3 + 'amp': 0x0026, # ampersand, U+0026 ISOnum + 'and': 0x2227, # logical and = wedge, U+2227 ISOtech + 'ang': 0x2220, # angle, U+2220 ISOamso + 'aring': 0x00e5, # latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1 + 'asymp': 0x2248, # almost equal to = asymptotic to, U+2248 ISOamsr + 'atilde': 0x00e3, # latin small letter a with tilde, U+00E3 ISOlat1 + 'auml': 0x00e4, # latin small letter a with diaeresis, U+00E4 ISOlat1 + 'bdquo': 0x201e, # double low-9 quotation mark, U+201E NEW + 'beta': 0x03b2, # greek small letter beta, U+03B2 ISOgrk3 + 'brvbar': 0x00a6, # broken bar = broken vertical bar, U+00A6 ISOnum + 'bull': 0x2022, # bullet = black small circle, U+2022 ISOpub + 'cap': 0x2229, # intersection = cap, U+2229 ISOtech + 'ccedil': 0x00e7, # latin small letter c with cedilla, U+00E7 ISOlat1 + 'cedil': 0x00b8, # cedilla = spacing cedilla, U+00B8 ISOdia + 'cent': 0x00a2, # cent sign, U+00A2 ISOnum + 'chi': 0x03c7, # greek small letter chi, U+03C7 ISOgrk3 + 'circ': 0x02c6, # modifier letter circumflex accent, U+02C6 ISOpub + 'clubs': 0x2663, # black club suit = shamrock, U+2663 ISOpub + 'cong': 0x2245, # approximately equal to, U+2245 ISOtech + 'copy': 0x00a9, # copyright sign, U+00A9 ISOnum + 'crarr': 0x21b5, # downwards arrow with corner leftwards = carriage return, U+21B5 NEW + 'cup': 0x222a, # union = cup, U+222A ISOtech + 'curren': 0x00a4, # currency sign, U+00A4 ISOnum + 'dArr': 0x21d3, # downwards double arrow, U+21D3 ISOamsa + 'dagger': 0x2020, # dagger, U+2020 ISOpub + 'darr': 0x2193, # downwards arrow, U+2193 ISOnum + 'deg': 0x00b0, # degree sign, U+00B0 ISOnum + 'delta': 0x03b4, # greek small letter delta, U+03B4 ISOgrk3 + 'diams': 0x2666, # black diamond suit, U+2666 ISOpub + 'divide': 0x00f7, # division sign, U+00F7 ISOnum + 'eacute': 0x00e9, # latin small letter e with acute, U+00E9 ISOlat1 + 'ecirc': 0x00ea, # latin small letter e with circumflex, U+00EA ISOlat1 + 'egrave': 0x00e8, # latin small letter e with grave, U+00E8 ISOlat1 + 'empty': 0x2205, # empty set = null set = diameter, U+2205 ISOamso + 'emsp': 0x2003, # em space, U+2003 ISOpub + 'ensp': 0x2002, # en space, U+2002 ISOpub + 'epsilon': 0x03b5, # greek small letter epsilon, U+03B5 ISOgrk3 + 'equiv': 0x2261, # identical to, U+2261 ISOtech + 'eta': 0x03b7, # greek small letter eta, U+03B7 ISOgrk3 + 'eth': 0x00f0, # latin small letter eth, U+00F0 ISOlat1 + 'euml': 0x00eb, # latin small letter e with diaeresis, U+00EB ISOlat1 + 'euro': 0x20ac, # euro sign, U+20AC NEW + 'exist': 0x2203, # there exists, U+2203 ISOtech + 'fnof': 0x0192, # latin small f with hook = function = florin, U+0192 ISOtech + 'forall': 0x2200, # for all, U+2200 ISOtech + 'frac12': 0x00bd, # vulgar fraction one half = fraction one half, U+00BD ISOnum + 'frac14': 0x00bc, # vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum + 'frac34': 0x00be, # vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum + 'frasl': 0x2044, # fraction slash, U+2044 NEW + 'gamma': 0x03b3, # greek small letter gamma, U+03B3 ISOgrk3 + 'ge': 0x2265, # greater-than or equal to, U+2265 ISOtech + 'gt': 0x003e, # greater-than sign, U+003E ISOnum + 'hArr': 0x21d4, # left right double arrow, U+21D4 ISOamsa + 'harr': 0x2194, # left right arrow, U+2194 ISOamsa + 'hearts': 0x2665, # black heart suit = valentine, U+2665 ISOpub + 'hellip': 0x2026, # horizontal ellipsis = three dot leader, U+2026 ISOpub + 'iacute': 0x00ed, # latin small letter i with acute, U+00ED ISOlat1 + 'icirc': 0x00ee, # latin small letter i with circumflex, U+00EE ISOlat1 + 'iexcl': 0x00a1, # inverted exclamation mark, U+00A1 ISOnum + 'igrave': 0x00ec, # latin small letter i with grave, U+00EC ISOlat1 + 'image': 0x2111, # blackletter capital I = imaginary part, U+2111 ISOamso + 'infin': 0x221e, # infinity, U+221E ISOtech + 'int': 0x222b, # integral, U+222B ISOtech + 'iota': 0x03b9, # greek small letter iota, U+03B9 ISOgrk3 + 'iquest': 0x00bf, # inverted question mark = turned question mark, U+00BF ISOnum + 'isin': 0x2208, # element of, U+2208 ISOtech + 'iuml': 0x00ef, # latin small letter i with diaeresis, U+00EF ISOlat1 + 'kappa': 0x03ba, # greek small letter kappa, U+03BA ISOgrk3 + 'lArr': 0x21d0, # leftwards double arrow, U+21D0 ISOtech + 'lambda': 0x03bb, # greek small letter lambda, U+03BB ISOgrk3 + 'lang': 0x2329, # left-pointing angle bracket = bra, U+2329 ISOtech + 'laquo': 0x00ab, # left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum + 'larr': 0x2190, # leftwards arrow, U+2190 ISOnum + 'lceil': 0x2308, # left ceiling = apl upstile, U+2308 ISOamsc + 'ldquo': 0x201c, # left double quotation mark, U+201C ISOnum + 'le': 0x2264, # less-than or equal to, U+2264 ISOtech + 'lfloor': 0x230a, # left floor = apl downstile, U+230A ISOamsc + 'lowast': 0x2217, # asterisk operator, U+2217 ISOtech + 'loz': 0x25ca, # lozenge, U+25CA ISOpub + 'lrm': 0x200e, # left-to-right mark, U+200E NEW RFC 2070 + 'lsaquo': 0x2039, # single left-pointing angle quotation mark, U+2039 ISO proposed + 'lsquo': 0x2018, # left single quotation mark, U+2018 ISOnum + 'lt': 0x003c, # less-than sign, U+003C ISOnum + 'macr': 0x00af, # macron = spacing macron = overline = APL overbar, U+00AF ISOdia + 'mdash': 0x2014, # em dash, U+2014 ISOpub + 'micro': 0x00b5, # micro sign, U+00B5 ISOnum + 'middot': 0x00b7, # middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum + 'minus': 0x2212, # minus sign, U+2212 ISOtech + 'mu': 0x03bc, # greek small letter mu, U+03BC ISOgrk3 + 'nabla': 0x2207, # nabla = backward difference, U+2207 ISOtech + 'nbsp': 0x00a0, # no-break space = non-breaking space, U+00A0 ISOnum + 'ndash': 0x2013, # en dash, U+2013 ISOpub + 'ne': 0x2260, # not equal to, U+2260 ISOtech + 'ni': 0x220b, # contains as member, U+220B ISOtech + 'not': 0x00ac, # not sign, U+00AC ISOnum + 'notin': 0x2209, # not an element of, U+2209 ISOtech + 'nsub': 0x2284, # not a subset of, U+2284 ISOamsn + 'ntilde': 0x00f1, # latin small letter n with tilde, U+00F1 ISOlat1 + 'nu': 0x03bd, # greek small letter nu, U+03BD ISOgrk3 + 'oacute': 0x00f3, # latin small letter o with acute, U+00F3 ISOlat1 + 'ocirc': 0x00f4, # latin small letter o with circumflex, U+00F4 ISOlat1 + 'oelig': 0x0153, # latin small ligature oe, U+0153 ISOlat2 + 'ograve': 0x00f2, # latin small letter o with grave, U+00F2 ISOlat1 + 'oline': 0x203e, # overline = spacing overscore, U+203E NEW + 'omega': 0x03c9, # greek small letter omega, U+03C9 ISOgrk3 + 'omicron': 0x03bf, # greek small letter omicron, U+03BF NEW + 'oplus': 0x2295, # circled plus = direct sum, U+2295 ISOamsb + 'or': 0x2228, # logical or = vee, U+2228 ISOtech + 'ordf': 0x00aa, # feminine ordinal indicator, U+00AA ISOnum + 'ordm': 0x00ba, # masculine ordinal indicator, U+00BA ISOnum + 'oslash': 0x00f8, # latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1 + 'otilde': 0x00f5, # latin small letter o with tilde, U+00F5 ISOlat1 + 'otimes': 0x2297, # circled times = vector product, U+2297 ISOamsb + 'ouml': 0x00f6, # latin small letter o with diaeresis, U+00F6 ISOlat1 + 'para': 0x00b6, # pilcrow sign = paragraph sign, U+00B6 ISOnum + 'part': 0x2202, # partial differential, U+2202 ISOtech + 'permil': 0x2030, # per mille sign, U+2030 ISOtech + 'perp': 0x22a5, # up tack = orthogonal to = perpendicular, U+22A5 ISOtech + 'phi': 0x03c6, # greek small letter phi, U+03C6 ISOgrk3 + 'pi': 0x03c0, # greek small letter pi, U+03C0 ISOgrk3 + 'piv': 0x03d6, # greek pi symbol, U+03D6 ISOgrk3 + 'plusmn': 0x00b1, # plus-minus sign = plus-or-minus sign, U+00B1 ISOnum + 'pound': 0x00a3, # pound sign, U+00A3 ISOnum + 'prime': 0x2032, # prime = minutes = feet, U+2032 ISOtech + 'prod': 0x220f, # n-ary product = product sign, U+220F ISOamsb + 'prop': 0x221d, # proportional to, U+221D ISOtech + 'psi': 0x03c8, # greek small letter psi, U+03C8 ISOgrk3 + 'quot': 0x0022, # quotation mark = APL quote, U+0022 ISOnum + 'rArr': 0x21d2, # rightwards double arrow, U+21D2 ISOtech + 'radic': 0x221a, # square root = radical sign, U+221A ISOtech + 'rang': 0x232a, # right-pointing angle bracket = ket, U+232A ISOtech + 'raquo': 0x00bb, # right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum + 'rarr': 0x2192, # rightwards arrow, U+2192 ISOnum + 'rceil': 0x2309, # right ceiling, U+2309 ISOamsc + 'rdquo': 0x201d, # right double quotation mark, U+201D ISOnum + 'real': 0x211c, # blackletter capital R = real part symbol, U+211C ISOamso + 'reg': 0x00ae, # registered sign = registered trade mark sign, U+00AE ISOnum + 'rfloor': 0x230b, # right floor, U+230B ISOamsc + 'rho': 0x03c1, # greek small letter rho, U+03C1 ISOgrk3 + 'rlm': 0x200f, # right-to-left mark, U+200F NEW RFC 2070 + 'rsaquo': 0x203a, # single right-pointing angle quotation mark, U+203A ISO proposed + 'rsquo': 0x2019, # right single quotation mark, U+2019 ISOnum + 'sbquo': 0x201a, # single low-9 quotation mark, U+201A NEW + 'scaron': 0x0161, # latin small letter s with caron, U+0161 ISOlat2 + 'sdot': 0x22c5, # dot operator, U+22C5 ISOamsb + 'sect': 0x00a7, # section sign, U+00A7 ISOnum + 'shy': 0x00ad, # soft hyphen = discretionary hyphen, U+00AD ISOnum + 'sigma': 0x03c3, # greek small letter sigma, U+03C3 ISOgrk3 + 'sigmaf': 0x03c2, # greek small letter final sigma, U+03C2 ISOgrk3 + 'sim': 0x223c, # tilde operator = varies with = similar to, U+223C ISOtech + 'spades': 0x2660, # black spade suit, U+2660 ISOpub + 'sub': 0x2282, # subset of, U+2282 ISOtech + 'sube': 0x2286, # subset of or equal to, U+2286 ISOtech + 'sum': 0x2211, # n-ary sumation, U+2211 ISOamsb + 'sup': 0x2283, # superset of, U+2283 ISOtech + 'sup1': 0x00b9, # superscript one = superscript digit one, U+00B9 ISOnum + 'sup2': 0x00b2, # superscript two = superscript digit two = squared, U+00B2 ISOnum + 'sup3': 0x00b3, # superscript three = superscript digit three = cubed, U+00B3 ISOnum + 'supe': 0x2287, # superset of or equal to, U+2287 ISOtech + 'szlig': 0x00df, # latin small letter sharp s = ess-zed, U+00DF ISOlat1 + 'tau': 0x03c4, # greek small letter tau, U+03C4 ISOgrk3 + 'there4': 0x2234, # therefore, U+2234 ISOtech + 'theta': 0x03b8, # greek small letter theta, U+03B8 ISOgrk3 + 'thetasym': 0x03d1, # greek small letter theta symbol, U+03D1 NEW + 'thinsp': 0x2009, # thin space, U+2009 ISOpub + 'thorn': 0x00fe, # latin small letter thorn with, U+00FE ISOlat1 + 'tilde': 0x02dc, # small tilde, U+02DC ISOdia + 'times': 0x00d7, # multiplication sign, U+00D7 ISOnum + 'trade': 0x2122, # trade mark sign, U+2122 ISOnum + 'uArr': 0x21d1, # upwards double arrow, U+21D1 ISOamsa + 'uacute': 0x00fa, # latin small letter u with acute, U+00FA ISOlat1 + 'uarr': 0x2191, # upwards arrow, U+2191 ISOnum + 'ucirc': 0x00fb, # latin small letter u with circumflex, U+00FB ISOlat1 + 'ugrave': 0x00f9, # latin small letter u with grave, U+00F9 ISOlat1 + 'uml': 0x00a8, # diaeresis = spacing diaeresis, U+00A8 ISOdia + 'upsih': 0x03d2, # greek upsilon with hook symbol, U+03D2 NEW + 'upsilon': 0x03c5, # greek small letter upsilon, U+03C5 ISOgrk3 + 'uuml': 0x00fc, # latin small letter u with diaeresis, U+00FC ISOlat1 + 'weierp': 0x2118, # script capital P = power set = Weierstrass p, U+2118 ISOamso + 'xi': 0x03be, # greek small letter xi, U+03BE ISOgrk3 + 'yacute': 0x00fd, # latin small letter y with acute, U+00FD ISOlat1 + 'yen': 0x00a5, # yen sign = yuan sign, U+00A5 ISOnum + 'yuml': 0x00ff, # latin small letter y with diaeresis, U+00FF ISOlat1 + 'zeta': 0x03b6, # greek small letter zeta, U+03B6 ISOgrk3 + 'zwj': 0x200d, # zero width joiner, U+200D NEW RFC 2070 + 'zwnj': 0x200c, # zero width non-joiner, U+200C NEW RFC 2070 +} + +codepoint2name = {} +entitydefs = {} + +for name in name2codepoint.keys(): + codepoint = name2codepoint[name] + codepoint2name[codepoint] = name + if codepoint <= 0xff: entitydefs[name] = unichr(codepoint) + else: entitydefs[name] = u'&#%d;' % codepoint + +UNQUOTE_ENTITY_NAMES = QUOTE_ENTITY_NAMES = tuple(filter( + lambda n: n not in ('amp',), name2codepoint.keys())) +UNQUOTE_HTML_NAMES = QUOTE_HTML_NAMES = ('lt', 'gt') +UNQUOTE_BUT_HTML_NAMES = QUOTE_BUT_HTML_NAMES = tuple(filter( + lambda n: n not in ('amp', 'lt', 'gt', 'quot'), name2codepoint.keys())) +UNQUOTE_ATTR_NAMES = QUOTE_ATTR_NAMES = ('lt', 'gt', 'quot') + +UNQUOTE_ENTITY_NAMES = UNQUOTE_ENTITY_NAMES + ('amp',) +QUOTE_ENTITY_NAMES = ('amp',) + QUOTE_ENTITY_NAMES + +UNQUOTE_HTML_NAMES = UNQUOTE_HTML_NAMES + ('amp',) +QUOTE_HTML_NAMES = ('amp',) + QUOTE_HTML_NAMES + +UNQUOTE_ATTR_NAMES = UNQUOTE_ATTR_NAMES + ('amp',) +QUOTE_ATTR_NAMES = ('amp',) + QUOTE_ATTR_NAMES + +def __quote(utext, encoding=None, names=QUOTE_HTML_NAMES): + recode = False + if type(utext) is not UnicodeType: + utext = _u(utext, encoding) + recode = True + for name in names: + char = unichr(name2codepoint[name]) + if utext.find(char) != - 1: + utext = utext.replace(char, u'&%s;' % name) + if recode: utext = _s(utext, encoding) + return utext + +def __unquote(utext, encoding=None, names=UNQUOTE_HTML_NAMES): + recode = False + if type(utext) is not UnicodeType: + utext = _u(utext, encoding) + recode = True + for name in names: + entity = u'&%s;' % name + if utext.find(entity) != - 1: + utext = utext.replace(entity, unichr(name2codepoint[name])) + if recode: utext = _s(utext, encoding) + return utext + +def quote_entity(utext, encoding=None): + return __quote(utext, encoding, QUOTE_ENTITY_NAMES) + +def quote_html(utext, encoding=None): + return __quote(utext, encoding, QUOTE_HTML_NAMES) + +def quote_but_html(utext, encoding=None): + return __quote(utext, encoding, QUOTE_BUT_HTML_NAMES) + +def quote_attr(utext, encoding=None): + return __quote(utext, encoding, QUOTE_ATTR_NAMES) + +def unquote_entity(utext, encoding=None): + return __unquote(utext, encoding, UNQUOTE_ENTITY_NAMES) + +def unquote_html(utext, encoding=None): + return __unquote(utext, encoding, UNQUOTE_HTML_NAMES) + +def unquote_but_html(utext, encoding=None): + return __unquote(utext, encoding, UNQUOTE_BUT_HTML_NAMES) + +def unquote_attr(utext, encoding=None): + return __unquote(utext, encoding, UNQUOTE_ATTR_NAMES) + +if __name__ == '__main__': + print u""" + + +Liste des entités + + +

Liste des entités qui sont pris en charge par ce module

+ +""".encode("utf-8") + for name, entity in entitydefs.items(): + print (u"" % (name, entity, name)).encode("utf-8") + print """
namechar (plain)char (entity)
%s%s&%s;
+ +""" diff --git a/pyulib/src/ulib/base/htmlentities.pyc b/pyulib/src/ulib/base/htmlentities.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8562d366749a58cc3c9747019ab0f6c99d0c2ced GIT binary patch literal 10703 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 T+S;ns|N8amk5aW}Y}LO3(L+>F literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/i_need_py23.py b/pyulib/src/ulib/base/i_need_py23.py new file mode 100644 index 0000000..13261fe --- /dev/null +++ b/pyulib/src/ulib/base/i_need_py23.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""L'importation de ce module permet de s'assurer que la version de python est +2.3 ou supérieure (mais pas 3.x) +""" + +import pversion; pversion.ensure_version("2.3") diff --git a/pyulib/src/ulib/base/i_need_py23.pyc b/pyulib/src/ulib/base/i_need_py23.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc98bc46870463addb7a929c6d6b79dd0df09685 GIT binary patch literal 362 zcmYLF%}N6?5T0%Qqo~g?;GyfKf%Yom!J~*Ec<3dhn}J4mH!+i_J^M7ih2RtTD!zc7 z*aZjj&CJL5WhVLlIhj1*=UoFsj3r;m;WGgXuoSGoj^L?+Wd&4$8-X3edj*xS_w`;8 z_X0Ol*99MwNzV6Z2^+%B+uRZciXElG@zgNqND-eiVQcV(A`kkoNo#Qur^$S_K%bFw z`1**B#DH~Y9Ahwyi`m=x#QjR#a#>fzb;;>aKx^w8t<#azVYob;)F Kx%|(;=oq#h24m&` literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/i_need_py24.py b/pyulib/src/ulib/base/i_need_py24.py new file mode 100644 index 0000000..566d7bf --- /dev/null +++ b/pyulib/src/ulib/base/i_need_py24.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""L'importation de ce module permet de s'assurer que la version de python est +2.4 ou supérieure (mais pas 3.x) +""" + +import pversion; pversion.ensure_version("2.4") diff --git a/pyulib/src/ulib/base/i_need_py24.pyc b/pyulib/src/ulib/base/i_need_py24.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f64726af98183cdc0cbdf66f4d2bd04e0c41a1b GIT binary patch literal 362 zcmYLF%}N6?5T0%Qqo~g?;GyfKf!d>p2ah6x;Gvh0ZU!3N-Na0y_UzO67J^UUtM~#s zu?r65o0*UA%S`h1eKfhpkJ|=@7)!pC!zTh3U@2IE9l>J-%L=FhHv&6`w+bp@@9Lc* z?gg%=t_waUlbr9-5;laLx49(@6gx_V~2!q(t3MIQ8Flh)!SPLug;fj%SW z@cAAci2>`*IL2TY&t|Wu6Za!=!)09&*CnTY0j;fXv`z<7hvD*cEDiN#t{$jb82AUi zl2)yKN9v(zjqBBm4{EzTt3D_1TUKF{Tengi8}(PL43l!YC$c(h=8KuUpv*nr8ZUOG L=JG##qeJ)wADLt6 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/i_need_py25.py b/pyulib/src/ulib/base/i_need_py25.py new file mode 100644 index 0000000..b1f9a43 --- /dev/null +++ b/pyulib/src/ulib/base/i_need_py25.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""L'importation de ce module permet de s'assurer que la version de python est +2.5 ou supérieure (mais pas 3.x) +""" + +import pversion; pversion.ensure_version("2.5") diff --git a/pyulib/src/ulib/base/i_need_py26.py b/pyulib/src/ulib/base/i_need_py26.py new file mode 100644 index 0000000..8dc3b8b --- /dev/null +++ b/pyulib/src/ulib/base/i_need_py26.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""L'importation de ce module permet de s'assurer que la version de python est +2.6 ou supérieure (mais pas 3.x) +""" + +import pversion; pversion.ensure_version("2.6") diff --git a/pyulib/src/ulib/base/input.py b/pyulib/src/ulib/base/input.py new file mode 100644 index 0000000..2ce635c --- /dev/null +++ b/pyulib/src/ulib/base/input.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Des fonctions pour la saisie de valeurs. +""" + +__all__ = ('I_BATCH', 'I_AUTO', 'I_NORMAL', 'I_INTER', + 'set_interaction', 'check_interaction', + 'is_yes', 'is_no', 'is_quit', + 'ask_yesno', 'ask_yesquit', 'read_value', + ) + +import os, sys, re + +from base import isnum +from uio import _s, _u, uinput +from output import eprint, V_ERROR, eerror + +################################################################################ +# Niveau d'interaction, pour la saisie de valeurs + +I_BATCH = 0 +I_AUTO = 1 +I_NORMAL = 2 +I_INTER = 3 + +I_MIN = I_BATCH +I_DEF = I_NORMAL +I_MAX = I_INTER + +def get_interaction(): return int(os.environ.get('__interaction', I_DEF)) +def _set_interaction(i): os.environ['__interaction'] = str(i) +def inc_interaction(): + i = get_interaction() + if i < I_MAX: _set_interaction(i + 1) +def dec_interaction(): + i = get_interaction() + if i > I_MIN: _set_interaction(i - 1) +def set_interaction(i): + if not isnum(i) and not i: i = I_DEF + elif i in ('-b', '--batch'): i = I_MIN + elif i in ('-y', '--automatic'): return dec_interaction() + elif i in ('-i', '--interactive'): return inc_interaction() + elif i in ('-c', '--default'): i = I_DEF + else: return False + _set_interaction(i) + return True + +INTERACTION_OPTS = ('-b', '--batch', + '-y', '--automatic', + '-i', '--interactive' + ) + +RE_NUM = re.compile(r'\d+$') +def check_interaction(i=I_NORMAL): + if isnum(i) or RE_NUM.match(i) is not None: return get_interaction() >= int(i) + elif i in ('-b', '--batch'): return get_interaction() >= I_BATCH + elif i in ('-y', '--automatic'): return get_interaction() >= I_AUTO + elif i in ('-c', '--default'): return get_interaction() >= I_NORMAL + elif i in ('-i', '--interactive'): return get_interaction() >= I_INTER + else: return True + +_set_interaction(get_interaction()) # configurer l'interaction par défaut + +# réponses +def is_yes(r): + return _s(r).lower() in ('o', 'oui', 'y', 'yes', 'v', 'vrai', 't', 'true', '1') + +def is_no(r): + return _s(r).lower() in ('n', 'non', 'no', 'f', 'faux', 'false', '0') + +def is_quit(r): + return is_no(r) or _s(r).lower() in ('q', 'quit', 'quitter') + +# lecture d'une réponse +def ask_yesno(msg=u"Voulez-vous continuer?", default=None, minlevel=I_NORMAL): + if default is None: default='N' + default = _s(default).upper() + + if not check_interaction(minlevel): + if default == 'C': return True + elif default == 'X': return False + else: return is_yes(default) + + if default == 'C': default = 'N' + elif default == 'X': default = 'O' + prompt = is_yes(default) and u"[On]" or u"[oN]" + eprint(u"%s %s " % (_u(msg), prompt), V_ERROR, False, True) + return is_yes(uinput() or default) + +def ask_yesquit(msg=u"Voulez-vous continuer?", default=None, minlevel=I_NORMAL): + if default is None: default = 'Q' + default = _s(default).upper() + + if not check_interaction(minlevel): + if default == 'C': return True + elif default == 'X': return False + else: return is_yes(default) + + if default == 'C': default = 'Q' + elif default == 'X': default = 'O' + prompt = is_yes(default) and u"[Oq]" or u"[oQ]" + eprint(u"%s %s " % (_u(msg), prompt), V_ERROR, False, True) + return is_yes(uinput() or default) + +def read_value(msg=u"Entrez la valeur", default=None, required=True, minlevel=I_NORMAL): + if not check_interaction(minlevel): + if required and default is None: + raise ValueError("default must not be empty") + return default + + if default is None: prompt = u'' + else: prompt = u' [%s]' % _u(default) + msg = u"%s%s: " % (_u(msg), prompt) + while True: + eprint(msg, V_ERROR, False, True) + r = uinput() + if r: break + elif required and default is None: + eerror(u"Vous devez entrer une valeur non vide") + else: + r = default + break + return r diff --git a/pyulib/src/ulib/base/input.pyc b/pyulib/src/ulib/base/input.pyc new file mode 100644 index 0000000000000000000000000000000000000000..576058d1bd3d59de25f8fb6ee886c4c9c1642543 GIT binary patch literal 5912 zcmeHL+frP|72RiM7+}Or2m!j;BO#=*6^yMUm6S`dg$OEBWeswJ6xS%KsnML4b>MK$ z=v=T-NiRW9`IY=cej!i!guLemlC^dZGXugYRf66Ebkn{2a(eH*_Ug;u{yAQGT>b0A zwse2S@&7JT`ot6Q@#o2oNCSmWrQsb)3eqSXmW$FT9+peeC~0{@c7`M@OQS3vXPK0p zVF^d1F(TopG)5(?NTcG~rex=ggk#belW<%b;}T9tV*+hwr7v9od@Z1VhTI_zWd7qn0n|GE~gioZzfaO4en3o9%PNN%d+y7)Ei zv#8xZk+(3=4N5j^zkrd0f8nQ|4Wqc;ytlHo`b}2W^2(#Fjci1V^$qi2<=bwb_4?MD z$tKZ|+N>EwnN3>lEQq7*EM)B$w!L#i%GrXnxo^{K$Q7elr-JZh9%R`lq^)#^JWOXn&AnEb+Zqrd-Fc#Xw^n4efUKFif4Qzc528+hYMjT&d1 zke_F#IMr3yXfH@$GB*8SGu3Fl`TMo|H3+1irC*`C{3719%iHa+6-3KF#+~Ke*v~_o zE=PG5$6>nM+0Vn^*>ak+m$mS$mD*)B!DpR)brrrbxUP>eYsxRRsWuZAqI%Au06kwT z^qjU(^$r54tED-I9?rBJlxB%JkN;+tVs($2@^p2W!sN|kc&kF@ltAIA5(GAsI1qWG zsz8rz*nV0(I033bqd7)a#OUg2rWjDojCz~STW>yC`QBVcNp&-qSVD5yX}Kqe+Jo-7 ziuqIGR=iPf+=20klTOmFbX33j_ttN{{=xP0?GgR1AJdPz9ntU45cEJeM!&&nV=>4Y zo`g%Irz-JBWxFgYE~xmv6IWC`g5E`9V3VW$Gd&@~_E0$1o50OGgfT<133k8`S`g8? zSz}z+Q-<%-vy@D^wDhc%wO=^VeoD?-T52I0cUxJ|cH$uAu%)FQL+)AE&`!yCOH00e z-pa#Fx#NV%2juU3VW91V&kVF4|JDe;Y{r<)Gb0a}DzlG}DGS|ig{f6QfZjy5k}m0< zKJpFczloGyL?+&>Q~-9Sk#4GZC9mS0U>hfT$1F~Kf<;n(>W}`d>+ka2U(nKnT4xp> zSB7`)_eHixC7yv)K?4i9hHZdEQRNVblR~#cS$a(lh5e@Cev`vuziFi3A4Fc}`N%eS6!qSG%YvgA01+&}8^xIKvenr8rDWF5OzoA?E z1~JR5a&q%IOGu}-!5c1W4e7Wk$VqC71r&OS=~9rR85}ha0H2bx3s>*#@={Hp=VQ*U z1vs;aviU7Cbv&i!OVpfPZA0&WP-W@gZ@2>I_neXQIL2x9)zV&^r`0xm8AQ2FzHW21 zv;oyxMN0pH?6uzSwnubV$1&}VCp@IVzi7dR>?{H)Lguv|zB{+E>0?wuwhIz-122)A zAu0AG%AnZOb&5?+*dM_gp!?3e6T4v)mwKN+92*ZqJS3eG{*~oTQE)!+hXwZ}=djmB zd0voN+-ZPKEaU(kL2qDh_>rT!OQ_LK&X+D@jCx!pN)nw# z3E>Tv(eAU5fe%>Mi^1!X20HE{KRsoj$B!_#o(>uAV1#b$k1+7xpkYY4>EX*gx2{Wr z>*8r|RKP1(xTifpNJFBX|A3L3H7Z)yH;8VmR(JC>t71p2KC@MeAl=W=MRHoB`eZRZ z#KU6xWi=;RfP~YkkH~&iwKQc?%_G~jCFY5$dx39j^oxTE$AGplknb*Qd?;}|q6DL~ zqKu+cLs~J@$aJB7_$Z?!7CTX5{XUjHz$VMX0z+Uj5kpzdu6Z2dyrn|x({D^{;VUISuQ%PpSGSKoe5F+N*h55m;=`BD<{rNE z+<{^g>r~x`FTDo#9=`Mv*{yNsPHFprhc6;PH`V{`3R=5~dGwJjXhzoh&CdSqU+N~% zY_`I%+0@4z-5t_>_okLufLVl(O76H`L@f_u-F_SqXf_HM(|V-CG% zh+w0KWkt2(mN4lvnU+rD4Ts;m<6h;yDxjR+MK*@B2!G4P>xC)rvUhoisKc+KuUr@f Z9!HD#1?jNYq&GG)&U$gQG(9;z`ERB4UBLhV literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/lines.py b/pyulib/src/ulib/base/lines.py new file mode 100644 index 0000000..b97248d --- /dev/null +++ b/pyulib/src/ulib/base/lines.py @@ -0,0 +1,397 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Des fonctions pour gérer des lignes de texte. +""" + +__all__ = ('BLines', 'Lines') + +import os, re +from types import MethodType, FunctionType + +from base import isstr, isseq +from uio import _s, _u, UnicodeIO, defaultIO +from encdetect import detect_line_encoding, guess_line_encoding +from functions import apply_args + +class BLines(list): + """Une liste de chaines de type str. + """ + _valid = False + def is_valid(self): return self._valid + valid = property(is_valid) + + _nl = None + def get_nl(self): return self._nl + def set_nl(self, nl): self._nl = nl + nl = property(get_nl, set_nl) + + def __strip_nl(self, line): + nl = '' + while line[-1:] in ('\r', '\n'): + nl = line[-1:] + nl + line = line[:-1] + if self._nl is None and nl: + self._nl = nl + return line + + def __add_nl(self, line): + if self._nl is None: self._nl = os.linesep + return line + self._nl + + def __init__(self, lines=None, nl=None): + if nl is not None: self._nl = nl + if lines is None: + pass + elif isseq(lines): + self[:] = map(self.__strip_nl, lines) + self._valid = True + if self._nl is None: self._nl = os.linesep + elif isstr(lines): + self.appendtext(lines) + if self._nl is None: self._nl = os.linesep + else: + raise ValueError("Unsupported type: %s" % type(lines)) + + def reset(self): + """Vider toutes les lignes de cet objet. + """ + self[:] = [] + self._valid = False + self._nl = None + + def _new(self, list=None): + u"""Retourner une instance vide de cette classe, avec les mêmes paramètres pour nl. + """ + return self.__class__(list, self._nl) + + def copy(self): + """Faire et retourner une copie de cet objet. + """ + return self._new(self) + + RE_NLs = re.compile(r'(\r?\n|\r)') + def appendtext(self, text, strip_last_nl=False): + """Ajouter un texte éventuellement sur plusieurs lignes. + + Si strip_last_nl, ignorer la dernière ligne vide. + + Equivalent en gros à self.extend(test.split(r'\r?\n|\r')) + + @return: self + """ + lines = self.RE_NLs.split(text) + if strip_last_nl and not lines[-1]: del lines[-1] + i, max = 0, len(lines) - 1 + while i <= max: + if i == max: + line = lines[i] + i += 1 + else: + line = lines[i] + lines[i + 1] + i += 2 + self.append(self.__strip_nl(line)) + return self + + def _after_readlines(self, lines, uio): + return lines + + def readlines(self, inf, raise_exception=True, uio=None, open_func=None, until=None): + """Lire les lignes du fichier. + + @param until: fonction qui si elle retourne True, arrête la lecture. La + ligne qui arrête la lecture est incluse dans la liste. La signature + de la fonction est until(line[, lines]). + @return: self + """ + self.reset() + close_inf = False + if isstr(inf): + if open_func is None: open_func = open + inf = open_func(inf, 'rb') + close_inf = True + try: + try: + if until: + lines = [] + while True: + line = inf.readline() + if not line: break + line = self.__strip_nl(line) + lines.append(line) + if apply_args(until, line, lines): break + else: + lines = map(self.__strip_nl, inf.readlines()) + lines = self._after_readlines(lines, uio) + self.extend(lines) + self._valid = True + except IOError: + if raise_exception: raise + finally: + if close_inf: inf.close() + if self._nl is None: self._nl = os.linesep + return self + + def _before_writelines(self, lines, uio): + if uio is None: uio = defaultIO + return map(uio.s, lines) + + def writelines(self, outf, uio=None, open_func=None): + """Ecrire les lignes dans le fichier + """ + close_outf = False + if isstr(outf): + if open_func is None: open_func = open + outf = open_func(outf, 'wb') + close_outf = True + try: + lines = self._before_writelines(self, uio) + outf.writelines(map(self.__add_nl, lines)) + finally: + if close_outf: outf.close() + + def _compile(self, forp): + if isstr(forp): forp = re.compile(forp) + return forp + + def __apply(self, func, line, index, lines, args): + f = func + ac_offset = 0 + if type(f) is MethodType: + f = f.im_func + ac_offset = 1 + if type(f) is not FunctionType: + raise ValueError("func must be a function") + argcount = f.func_code.co_argcount - ac_offset + args = (line, index, lines, args)[:argcount] + return func(*args) + + def _matches(self, forp, line, index=None, lines=None, args=None): + if callable(forp): return self.__apply(forp, line, index, lines, args) + else: return forp.match(line) is not None + + def grepi(self, forp, indexes=None, inverse=False, boundaries=False, **args): + """forp étant une fonction ou une expression régulière, retourner une + liste d'index de lignes pour lesquelles la fonction retourne True ou + qui correspondent à l'expression régulière forp. + + Si inverse==True, la logique est inversée (on ne retourne que les + indexes des lignes qui ne matches pas.) + + Si indexes n'est pas None, la recherche se limite aux lignes dont + l'index est dans cette liste. Sinon, si boundaries==True, ne rechercher + qu'au début et à la fin de la liste (i.e. s'arrêter de part et d'autre + dès que la fonction retourne False ou que le pattern ne matche pas.) + + @return: une liste d'indexes + @rtype: list + """ + if indexes is None: + indexes = range(len(self)) + else: + # ne pas chercher sur les boundaries si on donne indexes + boundaries = False + + forp = self._compile(forp) + min = 0 + matches = [] + for index in indexes: + line = self[index] + found = self._matches(forp, line, index, self, args) + if inverse: found = not found + if found: + matches.append(index) + elif boundaries: + min = index + break + if boundaries: + index = len(self) - 1 + pos = len(matches) + while index > min: + line = self[index] + found = self._matches(forp, line, index, self, args) + if inverse: found = not found + if found: + matches.insert(pos, index) + index -= 1 + else: + break + return matches + + def grep(self, forp, indexes=None, inverse=False, boundaries=False, **args): + """forp étant une fonction ou une expression régulière, retourner les + lignes pour lesquelles la fonction retourne True ou qui correspondent + à l'expression régulière forp. + + Consulter l'aide de la fonction grepi pour des détails sur les + paramètres. + + @return: une liste de chaines + @rtype: list + """ + indexes = self.grepi(forp, indexes, inverse, boundaries, **args) + return self._new(map(lambda i: self[i], indexes)) + + def replace(self, pattern, repl, count=0, indexes=None): + """Remplacer dans toute les lignes dont l'index est dans la liste + indexes (toutes les lignes par défaut), l'expression régulière pattern + par repl. + + @return: le nombre de remplacements effectués + @rtype: int + """ + if indexes is None: indexes = range(len(self)) + + pattern = self._compile(pattern) + nbrepl = 0 + for index in indexes: + line = self[index] + line, nb = pattern.subn(repl, line, count) + self[index] = line + nbrepl = nbrepl + nb + + return nbrepl + + def map(self, func, copy=False, **args): + """Pour chacune des lignes, appeler la fonction func avec les arguments + suivants dans cet ordre: + + line = la ligne + index = l'index dans la liste + lines = cet objet (ou sa copie) + args = le dictionnaire des paramètres supplémentaires + + Si copy==True, opérer sur une copie de cet objet, sinon les lignes sont + modifiées en place. + + @return: la liste résultat + @rtype: list + """ + if copy: lines = self.copy() + else: lines = self + + for index in range(len(lines)): + lines[index] = self.__apply(func, lines[index], index, lines, args) + return lines + + def filter(self, forp, copy=False, inverse=False, boundaries=False, **args): + """Pour chacune des lignes, essayer de matcher avec l'expression + régulière forp, ou appeler la fonction forp avec les arguments + suivants dans cet ordre: + + line = la ligne + index = l'index dans la liste + lines = cet objet (ou sa copie) + args = le dictionnaire des paramètres supplémentaires + + Si l'expression régulière matche, ou si la fonction retourne True, + la valeur est gardée, sinon elle est supprimée. Cette logique est + inversée si inverse==True. + + Si copy==True, opérer sur une copie de cet objet, sinon les lignes sont + modifiées en place. Si boundaries==True, n'opérer qu'au début et à la + fin de la liste (i.e. s'arrêter de part et d'autre dès que la fonction + retourne True.) + + @return: la liste résultat + @rtype: list + """ + if copy: lines = self.copy() + else: lines = self + + forp = self._compile(forp) + index = 0 + min = 0 + max = len(lines) + while index < max: + line = lines[index] + match = self._matches(forp, line, index, lines, args) + if inverse: match = not match + if match: + if boundaries: + min = index + break + else: + index += 1 + else: + del lines[index] + max -= 1 + if boundaries: + index = len(lines) - 1 + while index > min: + line = lines[index] + match = self._matches(forp, line, index, lines, args) + if inverse: match = not match + if match: + break + else: + del lines[index] + index -= 1 + return lines + + def join(self, lastnl=False): + """Obtenir les lignes jointes avec self.nl + + Si lastnl==True, la dernière ligne se termine par nl. Cela est + approprié quand on veut par exemple générer un fichier. + """ + if lastnl: + return ''.join(map(self.__add_nl, self)) + else: + nl = self._nl + if nl is None: nl = os.linesep + return nl.join(self) + +class Lines(BLines): + """Une liste de chaines de type unicode. + """ + __use_defaultIO = [] + __dont_convert = [] + def detect_encoding(lines): + encoding = detect_line_encoding(lines) + if encoding is None: + encoding = guess_line_encoding(lines, + Lines.__use_defaultIO, + Lines.__dont_convert) + return encoding + detect_encoding = staticmethod(detect_encoding) + + _uio = None + def get_uio(self): return self._uio + def set_uio(self, uio): self._uio = uio + uio = property(get_uio, set_uio) + + _encoding_detector = detect_encoding + def get_encoding_detector(self): return self._encoding_detector + def set_encoding_detector(self, encoding_detector): self._encoding_detector = encoding_detector + encoding_detector = property(get_encoding_detector, set_encoding_detector) + + def __init__(self, lines=None, nl=None, uio=None, encoding_detector=None): + if uio is not None: self._uio = uio + if encoding_detector is not None: + self._encoding_detector = encoding_detector + BLines.__init__(self, lines, nl) + + def _after_readlines(self, lines, uio): + set_uio = uio is None + if uio is None: uio = self._uio + if uio is None: + encoding_detector = self._encoding_detector + if encoding_detector is not None: + encoding = encoding_detector(lines) + if encoding is self.__use_defaultIO: + uio = defaultIO + if set_uio: self._uio = uio + elif encoding is self.__dont_convert: + pass + else: + uio = UnicodeIO(encoding) + if set_uio: self._uio = uio + if uio is not None: + lines = map(uio.u, lines) + return lines + + def _before_writelines(self, lines, uio): + if uio is None: uio = self._uio + if uio is None: uio = defaultIO + return map(uio.s, lines) diff --git a/pyulib/src/ulib/base/lines.pyc b/pyulib/src/ulib/base/lines.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46dbe1ce7f2c23bce43bdd95a5c38c2bba1671da GIT binary patch literal 15539 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 V_MIN: _set_verbosity(v - 1) +def set_verbosity(v): + if not isnum(v) and not v: v = V_DEF + elif v in ('-Q', '--very-quiet'): v = V_MIN + elif v in ('-q', '--quiet'): return dec_verbosity() + elif v in ('-v', '--verbose'): return inc_verbosity() + elif v in ('-c', '--default'): v = V_DEF + elif v in ('-D', '--debug'): v = V_MAX + else: return False + _set_verbosity(v) + return True + +VERBOSITY_OPTS = ('-Q', '--very-quiet', + '-q', '--quiet', + '-v', '--verbose', + '-D', '--debug' + ) + +RE_NUM = re.compile(r'\d+$') +def check_verbosity(v=V_INFO): + if isnum(v) or RE_NUM.match(v) is not None: return get_verbosity() >= int(v) + elif v in ('-Q', '--very-quiet'): return get_verbosity() >= V_ERROR + elif v in ('-q', '--quiet'): return get_verbosity() >= V_WARNING + elif v in ('-c', '--default'): return get_verbosity() >= V_INFO + elif v in ('-v', '--verbose'): return get_verbosity() >= V_DEBUG + elif v in ('-D', '--debug'): return is_debug() or get_verbosity() >= V_DEBUG + else: return True +def show_error(): return check_verbosity(V_ERROR) +def show_warn(): return check_verbosity(V_WARNING) +def show_info(): return check_verbosity(V_INFO) +def show_debug(): return is_debug() or check_verbosity(V_DEBUG) + +_set_verbosity(get_verbosity()) # configurer la verbosité par défaut + +################################################################################ +# Affichage des messages + +def get_tlevel(): return os.environ.get('__tlevel', '') +def set_tlevel(l): os.environ['__tlevel'] = l + +def eprint(msg, minlevel=V_INFO, nl=False, flush=None, out=None): + msgs = seqof(msg, []) + + if get_verbosity() == V_SYSLOG and syslog is not None: + if not nl: return # ignorer les lignes non complètes pour syslog + msg = '' + for mc in msgs: + if isseq(mc): + m = mc[0:1] and mc[0] or '' + msg += _u(m) + else: + msg += _u(mc) + ntlevel = get_ntlevel() + if ntlevel > 0: msg = u'[%i] %s' % (ntlevel, msg) + syslog.openlog(scriptname) + try: + syslog.syslog({V_NONE: syslog.LOG_EMERG, + V_ERROR: syslog.LOG_ERR, + V_WARNING: syslog.LOG_WARNING, + V_INFO: syslog.LOG_INFO, + V_DEBUG: syslog.LOG_DEBUG, + }.get(minlevel, syslog.LOG_INFO), + _s(msg)) + finally: + syslog.closelog() + elif check_verbosity(minlevel): + msg = '' + for mc in msgs: + if isseq(mc): + m = mc[0:1] and mc[0] or '' + c = mc[1:2] and mc[1] or '' + msg += get_color(c) + _u(m) + get_color(RESET) + else: + msg += _u(mc) + + tlevel = get_tlevel() + if tlevel: msg = _u(tlevel) + msg + uprint(msg, nl, flush, out=out) + +def __add_suffix(msg, cls, obj, tb, show_tb=False): + # si (cls, obj, tb) sont des informations sur une exception, ajouter la + # description de l'exception au message msg. Sinon, msg n'est pas modifié + # (et reste à la valeur None si c'était le cas) + nl = True + if obj: + if not msg: msg = _u(obj) + else: msg = _u(msg) + u': ' + _u(obj) + if tb is not None and (show_tb or show_debug()): + lines = format_exception(cls, obj, tb) + lines.insert(0, '\n') + if msg is None: msg = u'' + msg += u''.join(map(_u, lines)) + nl = False + return msg, nl + +def eerror(msg=None, flush=None, show_tb=False): + nl = True + if msg is None or show_tb: + cls, obj, tb = sys.exc_info() + msg, nl = __add_suffix(msg, cls, obj, tb, show_tb) + eprint((('error: ', 'r,@'), msg), V_ERROR, nl, flush, ERR) +def ewarn(msg, flush=None): eprint((('warning: ', 'y,@'), msg), V_WARNING, True, flush, ERR) +def enote(msg, flush=None): eprint((('note: ', 'g,@'), msg), V_INFO, True, flush, ERR) +def eimportant(msg, flush=None): eprint((('important: ', 'r,@'), msg), V_ERROR, True, flush, ERR) +def eattention(msg, flush=None): eprint((('attention: ', 'y,@'), msg), V_WARNING, True, flush, ERR) +def einfo(msg, flush=None): eprint((('info: ', 'b,@'), msg), V_INFO, True, flush, ERR) +def eecho(msg, flush=None): eprint(msg, V_INFO, True, flush, ERR) +def eecho_(msg, flush=None): eprint(msg, V_INFO, False, flush, ERR) +def edebug(msg, flush=None): eprint((('debug: ', 'w,@'), msg), V_DEBUG, True, flush, ERR) + +def get_ntlevel(): return len(get_tlevel()) / 4 +def set_ntlevel(l): set_tlevel(l * ' ') +def inc_ntlevel(): + if get_estack() != '': set_ntlevel(get_ntlevel() + 1) +def dec_ntlevel(): + l = get_ntlevel() + if l > 0: set_ntlevel(l - 1) + +def get_estack(): return os.environ.get('__estack', '') +def set_estack(s): os.environ['__estack'] = s +def add_estack(c): set_estack('%s:%s' % (get_estack(), c)) +def check_estack(c): return get_estack()[-1:] == c +def rem_estack(c): + estack = get_estack() + if estack.endswith(c): set_estack(estack[:-2]) + +def _result(msg, func_or_result, *args, **kw): + r = None + exc_info = (None, None) + if callable(func_or_result): + func = func_or_result + try: + r = func(*args, **kw) + except: + r = False + cls, obj, tb = sys.exc_info() + msg, _ = __add_suffix(msg, cls, obj, tb) + del tb + exc_info = (cls, obj) + else: + r = func_or_result + if r is None: r = True + return r, msg, exc_info +def _raise(exc_info): + cls, obj = exc_info[:2] + if cls is None and obj is None: pass + elif isinstance(obj, cls): raise obj + else: raise cls(obj) +def etitle(title, func_or_result=None, *args, **kw): + estack = get_estack() + inc_ntlevel() + add_estack('t') + eprint((('+++ ', 'b,@'), (title, 'b,@,_')), V_INFO, True, True, ERR) + if func_or_result is not None: eend(func_or_result, *args, **kw) +def estep(msg, func_or_result=None, flush=None): + r, msg, exc_info = _result(msg, func_or_result) + eprint((('* ', 'w,@'), msg), V_INFO, True, flush, ERR) + _raise(exc_info) +def estep_(msg, func_or_result=None, flush=None): + r, msg, exc_info = _result(msg, func_or_result) + eprint((('* ', 'w,@'), msg), V_INFO, False, flush, ERR) + _raise(exc_info) +def ebegin(title, func_or_result=None, *args, **kw): + add_estack('b') + eprint((('* ', 'w,@'), title + ': '), V_INFO, False, True, ERR) + if func_or_result is not None: eend(func_or_result, *args, **kw) +def _edoto(): eprint('.', V_INFO, False, True, ERR) +def _edotx(): eprint((('x', 'r,@'),), V_INFO, False, True, ERR) +def _edotd(msg): + msg = list(seqof(msg, [])) + msg.insert(0, '(') + msg.append(')') + eprint(msg, V_INFO, True, True, ERR) +def edot(msg=None, func_or_result=None, *args, **kw): + r, msg, exc_info = _result(msg, func_or_result, *args, **kw) + if not show_info(): return + if r: _edoto() + else: _edotx() + if msg and show_debug(): _edotd(msg) + _raise(exc_info) +def _eendo(): eprint((('[ok]', 'g,@'),), V_INFO, True, True, ERR) +def _eendx(): eprint((('[error]', 'r,@'),), V_INFO, True, True, ERR) +def eend(func_or_result=None, *args, **kw): + r, msg, exc_info = _result(None, func_or_result, *args, **kw) + if check_estack('b'): + # terminer une section commencée par ebegin + rem_estack('b') + if show_info(): + if r: _eendo() + else: _eendx() + if msg and show_debug(): _edotd(msg) + elif check_estack('t'): + # terminer une section commencée par etitle + rem_estack('t') + dec_ntlevel() + _raise(exc_info) +def resets(): + set_estack('') + set_ntlevel(0) diff --git a/pyulib/src/ulib/base/output.pyc b/pyulib/src/ulib/base/output.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbf543ce5f295d7431cab05bb3d36638f62ab7e2 GIT binary patch literal 18920 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 '/' + """ + return path.split(file)[0] + +def basename(file, strip_ext=None): + """Retourner le nom de base de file, en enlevant éventuellement l'extension strip_ext + """ + bn = path.split(file)[1] + if strip_ext is not None: + n, e = path.splitext(bn) + if e == strip_ext: bn = n + return bn + +RE_ROOT = re.compile(r'/*$') +def splitall(file): + """Retourner tous les composants de file (répertoires et fichier) + + Par exemple: + splitall("/a/b") --> ["a", "b"] + Cas particuliers: + splitall("") --> [] + splitall("/") --> [] + """ + dirs = [] + while RE_ROOT.match(file) is None: + dir, name = path.split(file) + dirs.insert(0, name) + file = dir + return dirs + +def _join(dir, name): + return path.normpath(path.join(dir, name)) + +def abspath(file, basedir=None, cwd=None): + """Retourner le chemin absolu de file. + + file est exprimé par rapport au répertoire basedir, qui est exprimé par + rapport à cwd s'il est relatif. + """ + if path.isabs(file): return file + if basedir is not None: + file = _join(basedir, file) + if path.isabs(file): return file + if cwd is None: cwd = os.getcwd() + return _join(cwd, file) + +def abspath2(file, basedir=None, cwd=None): + """Retourner le chemin absolu de file. + + Si file a un chemin ou s'il existe dans cwd, il est exprimé par rapport à cwd. + Si file est sans chemin et qu'il n'existe pas dans cwd, il est exprimé par + rapport à basedir. + """ + if path.isabs(file): return file + if cwd is None: cwd = os.getcwd() + cwdfile = _join(cwd, file) + if path.exists(cwdfile): return cwdfile + if basedir is not None: + dir, _ = path.split(file) + if dir == "": file = _join(basedir, file) + if path.isabs(file): return file + return _join(cwd, file) + +def relpath(file, basedir=None, cwd=None): + """Retourner le chemin relatif pour accéder à file depuis cwd. + file est exprimé par rapport au répertoire basedir. + """ + if cwd is None: cwd = os.getcwd() + cwd = path.abspath(cwd) + cwdslash = cwd + '/' + if basedir is None: basedir = cwd + if not path.isabs(file): file = path.abspath(path.join(basedir, file)) + file = path.normpath(file) + + if file.startswith(cwdslash): + file = file[len(cwdslash):] + elif file == cwd: + file = '.' + else: + prefix = path.commonprefix([file, cwd]) + if not prefix.endswith('/'): + pos = prefix.rfind('/') + if pos != - 1: prefix = prefix[:pos + 1] + file = file[len(prefix):] + cwd = cwd[len(prefix):] + levels = len(splitall(cwd)) + relpath = '' + for _ in range(levels): + relpath = path.join('..', relpath) + file = path.join(relpath, file) + return file + +def ppath(file, basedir=None, cwd=None, homedir=None): + """Retourner si possible le chemin relatif pour accéder à file depuis cwd, + homedir, ou le chemin absolu, suivant les cas exposés ci-dessous. file est + exprimé par rapport au répertoire basedir. + + Le chemin retourné par cette fonction est destiné à l'affichage. + + - Si file est situé dans l'arborescence à partir de cwd, alors retourner un + chemin du genre relpath/to/file. + - Si file est situé dans le répertoire $HOME, alors retourner un chemine du + genre ~/path/to/file + - Sinon, retourner le chemin absolu /path/to/file + """ + if cwd is None: cwd = os.getcwd() + cwd = path.abspath(cwd) + if basedir is None: basedir = cwd + if not path.isabs(file): file = path.abspath(path.join(basedir, file)) + file = path.normpath(file) + if homedir is None: homedir = path.expanduser('~') + homedir = path.abspath(homedir) + # depuis cwd + ppath = relpath(file, cwd=cwd) + if not ppath.startswith('../'): return ppath + # depuis ~ + ppath = relpath(file, cwd=homedir) + if ppath == '.': return '~' + elif not ppath.startswith('../'): return '~/' + ppath + # sinon chemin absolu + return file + +def mkdirp(dir): + """Créer si nécessaire le répertoire + + @return: True si le répertoire a été créé, False sinon + """ + if not path.isdir(dir): + os.makedirs(dir) + return True + return False + +def mkdirof(file): + """Créer si nécessaire le répertoire du fichier file + + @return: True si le répertoire a été créé, False sinon + """ + dir, _ = path.split(file) + return mkdirp(dir) diff --git a/pyulib/src/ulib/base/paths.pyc b/pyulib/src/ulib/base/paths.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84b341a570a4f15d6c19d1a7f961dc68800b7f74 GIT binary patch literal 8931 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/procs.py b/pyulib/src/ulib/base/procs.py new file mode 100644 index 0000000..a861796 --- /dev/null +++ b/pyulib/src/ulib/base/procs.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Des fonctions pour lancer et gérer des processus externes +""" + +__all__ = ('is_trace', 'is_stop_on_errors', 'spawn', 'spawnall', 'spawnone', + 'LinePumper', 'DataPumper', 'spawn_capture', 'spawn_redirect') + +import os, sys, re +from select import select +from threading import Thread +try: + import subprocess +except ImportError: + def __spawnlp(mode, cmd, *args): + return os.spawnlp(mode, cmd, cmd, *args) + + from popen2 import Popen3, Popen4 + + def __spawn_capture(capture_out, copy_err_on_out, capture_err, cmd, *args): + cmd = (cmd,) + args + if copy_err_on_out: child = Popen4(cmd) + else: child = Popen3(cmd, True) + + if capture_out: + outt = StringDataPumper(child.fromchild, None, close_on_eof=False) + if capture_err: + errt = StringDataPumper(child.childerr, None, close_on_eof=False) + + if capture_out: outt.join() + if capture_err: errt.join() + + exitcode = child.wait() + child.fromchild.close() + if capture_err: child.childerr.close() + child.tochild.close() + + if capture_out: stdout = outt.buffer or "" + else: stdout = None + if capture_err: stderr = errt.buffer or "" + else: stderr = None + + return Status(exitcode=exitcode), stdout, stderr + + def __spawn_redirect(inf, outf, errf, copy_errf_on_outf, cmd, *args): + cmd = (cmd,) + args + if copy_errf_on_outf: child = Popen4(cmd) + else: child = Popen3(cmd, True) + + outt = DataPumper(child.fromchild, outf, close_on_eof=False) + if errf is not None: + errt = DataPumper(child.childerr, errf, close_on_eof=False) + int = LinePumper(inf, child.tochild, close_on_eof=True) + + outt.join() + if errf is not None: + errt.join() + int.stop() + + return child.wait() +else: + def __spawnlp(mode, cmd, *args): + return subprocess.call([cmd] + list(args)) + + def __spawn_capture(capture_out, copy_err_on_out, capture_err, cmd, *args): + cmd = (cmd,) + args + stdout = subprocess.PIPE + if capture_out and copy_err_on_out: stderr = subprocess.STDOUT + else: stderr = subprocess.PIPE + proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr, close_fds=True) + stdout, stderr = proc.communicate() + if not capture_out: stdout = None + if not capture_err: stderr = None + return Status(exitcode=proc.returncode), stdout, stderr + + def __spawn_redirect(inf, outf, errf, copy_errf_on_outf, cmd, *args): + cmd = (cmd,) + args + if copy_errf_on_outf: errf = subprocess.STDOUT + proc = subprocess.Popen(cmd, stdin=inf, stdout=outf, stderr=errf) + proc.communicate() + return proc.returncode + +from base import isstr +from uio import _s +from control import Status, OK_STATUS + +TRACE = 'trace' +STOP_ON_ERRORS = 'stop_on_errors' +STDIN = 'stdin' +STDOUT = 'stdout' +STDERR = 'stderr' + +def is_trace(kw): + """Indiquer si spawn doit afficher le nom de la commande qui est lancée. + """ + return kw.get(TRACE, False) +def is_stop_on_errors(kw): + """Indiquer si spawnall doit s'arrêter en cas d'erreur. + """ + return kw.get(STOP_ON_ERRORS, True) + +RE_ESCAPE = re.compile(r"\s|'") +def __quote(s): + if RE_ESCAPE.search(s) is not None: + s = s.replace("'", "'\''") + return "'%s'" % s + return s + +def spawn(cmd, *args, **kw): + """Lancer la commande cmd avec les arguments args, et retourner son status + d'exécution. La commande est cherchée dans le PATH si nécessaire. + + @rtype: Status + @raise OSError: Si la commande n'est pas trouvée dans le PATH. + """ + if is_trace(kw): + cmdline = "$ %s" % cmd + for arg in args: + cmdline += " %s" % __quote(arg) + print cmdline + exitcode = __spawnlp(os.P_WAIT, cmd, *args) + if exitcode == 127: + raise OSError(exitcode, "Command not found: %s" % _s(cmd)) + return Status(exitcode) + +def spawnall(*argss, **kw): + """Lancer toutes les commandes de argss, en s'arrêtant à la première erreur. + + Si kw[STOP_ON_ERRORS] == False, on lance toutes les commande sans se + préoccuper de leur code de retour. + Retourner le status d'exécution de la dernière commande lancée. + + @rtype: Status + """ + stop_on_errors = is_stop_on_errors(kw) + status = OK_STATUS + for args in argss: + status = spawn(*args, **kw) + if not status and stop_on_errors: break + return status + +def spawnone(*argss, **kw): + """Lancer toutes les commandes de argss, en s'arrêtant à la première + commande qui s'exécute sans erreur. + + @rtype: Status + """ + status = OK_STATUS + for args in argss: + status = spawn(*args, **kw) + if status: break + return status + +class Pumper(Thread): + """Un thread qui lit les données sur un flux et l'écrit sur un autre. + + En principe, outf est un objet fichier. + Si outf est None, alors les méthodes write_data, flush_outf et close_outf + doivent être surchargées pour implémenter la nouvelle stratégie d'écriture. + """ + def __init__(self, inf, outf, + stop_on_eof=True, close_on_eof=True, + flush_on_write=True, + start=True): + Thread.__init__(self) + self.setDaemon(True) + self.pumping = True + self.inf = inf + self.outf = outf + self.stop_on_eof = stop_on_eof + self.close_on_eof = close_on_eof + self.flush_on_write = flush_on_write + if start: self.start() + + def read_data(self): + raise NotImplementedError + def write_data(self, data): + self.outf.write(data) + def flush_outf(self): + self.outf.flush() + def close_outf(self): + self.outf.close() + + def run(self): + while self.pumping: + ins = [self.inf] + if self.outf is None: outs = [] + else: [self.outf] + inr, outr, _ = select(ins, outs, [], 0.1) + if self.inf in inr and (not outs or self.outf in outr): + data = self.read_data() + if data: + self.write_data(data) + if self.flush_on_write: self.flush_outf() + elif self.stop_on_eof: + if self.close_on_eof: self.close_outf() + break + + def stop(self): + """Arrêter le pompage dès que possible. + """ + self.pumping = False + self.join() + +class LinePumper(Pumper): + """Un pumper qui lit les données ligne par ligne + """ + def read_data(self): + return self.inf.readline() + +class DataPumper(Pumper): + """Un pumper qui lit les données par quantité de bufsize. + """ + bufsize = 4096 + + def set_bufsize(self, bufsize): + self.bufsize = bufsize + + def read_data(self): + return self.inf.read(self.bufsize) + +class StringPumperMixin: + """Un mixin à utiliser avec les classes dérivées de Pumper pour écrire + dans une chaine plutôt que sur un flux. + """ + buffer = None + + def write_data(self, data): + buffer = self.buffer + if buffer is None: buffer = "" + buffer += data + self.buffer = buffer + def flush_outf(self): pass + def close_outf(self): pass + +class StringLinePumper(StringPumperMixin, LinePumper): + pass + +class StringDataPumper(StringPumperMixin, DataPumper): + pass + +def spawn_capture(cmd, *args, **kw): + """Lancer la commande cmd avec les arguments args, et retourner son status + d'exécution, ainsi que sa sortie standard et (éventuellement) sa sortie + d'erreur sous forme de chaine. + + Si kw ne contient pas la clé stdout avec une valeur fausse, la sortie + standard sera capturée. Si kw contient la clé stderr avec une valeur vraie, + la sortie d'erreur sera capturée. Si kw contient la clé stderr avec une + valeur fausse, la sortie d'erreur ne sera pas capturée. Sinon, la sortie + d'erreur sera connectée sur la sortie standard. + + @rtype: tuple + @return: (status, stdout, stderr) + """ + capture_out = kw.get(STDOUT, True) and True or False + capture_err = kw.get(STDERR, None) + if capture_err is None: + copy_err_on_out = True + else: + copy_err_on_out = False + capture_err = capture_err and True or False + return __spawn_capture(capture_out, copy_err_on_out, capture_err, cmd, *args) + +def spawn_redirect(cmd, *args, **kw): + """Lancer la commande cmd avec les arguments args, et retourner son status + d'exécution. + + Si kw contient la clé stdin, l'entrée standard de cmd sera connectée sur + ce fichier. Si kw contient la clé stdout, la sortie standard de cmd sera + connectée sur ce fichier. Si kw ne contient pas la clé stderr, la sortie + d'erreur de cmd sera connectée sur stdout si défini. + + Les valeurs stdout et stderr qui sont retournées sont les valeurs prises + de kw telles quelles. + + @rtype: tuple + @return: (status, stdout, stderr) + """ + inf = stdin = kw.get(STDIN, None); close_inf = False + outf = stdout = kw.get(STDOUT, None); close_outf = False + errf = stderr = kw.get(STDERR, None); close_errf = False + if stdin is None and stdout is None and stderr is None: + return spawn(cmd, *args, **kw), None, None + + if inf is None: + inf = sys.stdin + elif isstr(inf): + inf = open(inf, 'rb') + close_inf = True + if outf is None: + outf = sys.stdout + elif isstr(outf): + outf = open(outf, 'wb') + close_outf = True + copy_errf_on_outf = errf is None + if isstr(errf): + errf = open(errf, 'wb') + close_errf = True + + if is_trace(kw): + cmdline = "$ %s" % cmd + for arg in args: + cmdline += " %s" % __quote(arg) + print cmdline + + try: + exitcode = __spawn_redirect(inf, outf, errf, copy_errf_on_outf, cmd, *args) + return Status(exitcode=exitcode), stdout, stderr + finally: + if close_errf: errf.close() + if close_outf: outf.close() + if close_inf: inf.close() diff --git a/pyulib/src/ulib/base/procs.pyc b/pyulib/src/ulib/base/procs.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3828e3fcd80dd44e73426e8baf4209d61d307831 GIT binary patch literal 13576 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/pversion.py b/pyulib/src/ulib/base/pversion.py new file mode 100644 index 0000000..265d019 --- /dev/null +++ b/pyulib/src/ulib/base/pversion.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""Gestion de la version des produits installés. + +Pour le moment, seule la version de python est gérée. +""" + +__all__ = ('get_version', 'check_version', + 'VersionMismatchError', 'ensure_version', + ) + +import sys, re + +PYTHON = 'python' +RE_SEP = re.compile(r'\.') + +def __unknown_product(product): + return ValueError("unknown product: %s" % product) + +R_MAP = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc', 'final': ''} + +def get_version(product=PYTHON): + if product == PYTHON: + a, b, c = sys.version_info[:3] + r = sys.version_info[3]; r = R_MAP.get(r, r) + s = sys.version_info[4] + if not r: s = s or '' + return '%i.%i.%i%s%s' % (a, b, c, r, s) + else: + raise __unknown_product(product) + +_PYTHON_VERSION_CACHE = {} +def check_version(version, product=PYTHON): + """Vérifier la version minimum de product. + + Pour le moment, product ne peut valoir que 'python'. version est une chaine + de la forme "x[.y...]". + + La version majeure doit correspondre. Les versions mineures doivent être + supérieure ou égales à la version donnée. + """ + if product == PYTHON: + check = _PYTHON_VERSION_CACHE.get(version, None) + if check is None: + expected = tuple(map(int, RE_SEP.split(version))) + actual = tuple(sys.version_info[:len(expected)]) + check = actual[0:1] == expected[0:1] and actual[1:] >= expected[1:] + _PYTHON_VERSION_CACHE[version] = check + return check + else: + raise __unknown_product(product) + +class VersionMismatchError(Exception): + pass + +def ensure_version(version, product=PYTHON): + if not check_version(version, product): + raise VersionMismatchError("Expected %s %s, got %s" % (product, version, get_version(product))) diff --git a/pyulib/src/ulib/base/pversion.pyc b/pyulib/src/ulib/base/pversion.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db8afa6feb64d9b9b0e4093e14b4903aad6c3874 GIT binary patch literal 2656 zcmb_ePjA#l6o0m}n`B9Vgpk6a?Ldpj0qvejs|r=6XjLk~5;;{Q7AniJXR{N$w(A)K zk&;W{-fNFNHof;=DW9NUrC*@^y=R92?J?HQ%$u1v^Zxwad$WK2z1-VYe{Lq!ejSWo zquJlkr9@-&>ooQ#?NHjKXFiSn6FQ)AK*6dsKC=B@Z?mW?m1#9A zW;%Dbl+`t6?;KU-zMJAo+^lwwkE-J%-S4?`5Vosb)QWJ+m?u-6JbSaUhL!C`frrM< zVwX&ZRaI2(JT`Q0>q?*Snt$+(To)USFzbVS+D|+T+yb(K?*HwZW_- zdkKA=Kg)|3d1GqgzEn5t2A_64blY)OYf+$Co5z~?Byk%cNjiHM)L;r{4EB;NHu>O1 zQ4VJCT&C?HuU%1Oc2Mrunb{fGDj5i|6I(qfTTk?1!3u7PqE?-#mBNc+7kQ4giXF#V zoR!m9<~wc<^BwJC$0k*YTf~sWd1}(wX;=+Q_{hZMaVD-|hp`>vnx8?yDd5}lsi;JX zL9BYDo(6R2(Vv4&?U=+gNF~eY$$s;f->Iyn_yvbL1fwv6g$F zZ~zGSbP&kl4oy0=zU4tZXaH1xLckKRVRc7_(SsPGkm0jS=x&(4{BBra#AXQin~%T0 z|7ZlIz_o83GqzrhOg<^Z`Y?KUcT-ppej!^g=R>|ET;Pt;7LO*scA9%y?a#VQ!Ue3luXqYSF!-(A&+vvtWP%QZ!)Q@Ui#+G5*jQ}fEHa$oqYbW#;YD=rGPNq>|9APh9Lu+zmaoOYi_{h8J4ZQPS$4{^&AM<^KW<@c)f*yrijW(P{ zqHh%C@k~dNRP!imigFa4>MYdRRPd0ce#Qzh(e)&mg~U{n`EwA+WaJH}icjSylF!ID zu8i12GD{9k0(qxU5v6i+Rpg6CmTSkb#Q-lAez(+a;rP+w0vNJSL=UMnawt^1#+B~W z>N7H!MSg*1ITFdc=ru|I-ZO9i^UPZg-~W5&<*9!6ncuhoUJ1)6N{a+ga)?F|{*6SD zgmUxp=_X}(Gbv`J$#nQ3XbJ6*AA0dfc!eqUl01~dmx(Ucw&&DIXBZk1i+7sn f`--8ktVh=;uNz$PyWRzVF}Ut8^}62D+IsLWVHRNI literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/times.py b/pyulib/src/ulib/base/times.py new file mode 100644 index 0000000..0108125 --- /dev/null +++ b/pyulib/src/ulib/base/times.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Des fonction pour gérer les heures. +""" + +__all__ = ('pytime', + 'NUM_TIMEF', 'FR_TIMEF', 'HM_TIMEF', + 'Time', 'istime', 'isanytime', + 'parse_time', 'ensure_time', + ) + +import re +from time import time as timedbl +from time import localtime +from datetime import time as pytime, timedelta + +from base import isstr, isnum +from uio import _s, _u + +NUM_TIMEF = '%H%M%S' +FR_TIMEF = '%H:%M:%S' +HM_TIMEF = '%H:%M' + +def _fix_second(minute, second): + while second > 59: + second -= 60 + minute += 1 + while second < 0: + second += 60 + minute -= 1 + return minute, second +def _fix_minute(hour, minute): + while minute > 59: + minute -= 60 + hour += 1 + while minute < 0: + minute += 60 + hour -= 1 + return hour, minute +def _fix_hour(hour): + return hour % 24 +def _fix_time(hour, minute, second): + minute, second = _fix_second(minute, second) + hour, minute = _fix_minute(hour, minute) + hour = _fix_hour(hour) + return hour, minute, second + +class Time(object): + """Un wrapper pour 'datetime.time'. + + Attention! Cet objet est mutable, il ne faut donc pas l'utiliser comme clé + dans un dictionnaire. + """ + _t = None + + time = property(lambda self: self._t) + hour = property(lambda self: self._t.hour) + minute = property(lambda self: self._t.minute) + second = property(lambda self: self._t.second) + + def __init__(self, hour=None, minute=None, second=None, t=None, s=None): + """Initialiser l'objet + + Dans l'ordre, les champs considérés sont: + - s le nombre de secondes depuis minuit. + - hour, minute, second la spécification de l'heure. + - t le nombre de secondes depuis l'epoch, comme retourné par + time.time(). + - sinon prendre l'heure courante. + """ + if s is not None: + hour, s = s / 3600, s % 3600 + minute, second = s / 60, s % 60 + elif hour is None and minute is None and second is None: + if t is None: t = timedbl() + hour, minute, second = localtime(t)[3:6] + hour, minute, second = _fix_time(hour or 0, minute or 0, second or 0) + self._t = pytime(hour, minute, second) + + FORMAT_MAP = {'%Y': '%(y)04i', '%m': '%(m)02i', '%d': '%(d)02i', + '%H': '%(H)02i', '%M': '%(M)02i', '%S': '%(S)02i', + } + def format(self, format=None): + """Formater l'heure pour affichage. + + Les champs valides sont %Y, %m, %d qui valent toujours 0, et %H, %M, %S + qui correspondent à l'heure de cet objet. + """ + if format is None: format = FR_TIMEF + y, m, d, H, M, S = 0, 0, 0, self.hour, self.minute, self.second + for fr, to in self.FORMAT_MAP.items(): + format = format.replace(fr, to) + return format % locals() + + def nbsecs(self): + """Retourner le nombre de secondes depuis minuit. + """ + return self.hour * 3600 + self.minute * 60 + self.second + nbsecs = property(nbsecs) + + def __repr__(self): + if self.second == 0: seconds = '' + else: seconds = ', %i' % self.second + return '%s(%i, %i%s)' % (self.__class__.__name__, self.hour, self.minute, seconds) + def __str__(self): + if self.second == 0: seconds = '' + else: seconds = ':%02i' % self.second + return '%02i:%02i%s' % (self.hour, self.minute, seconds) + def __unicode__(self): + if self.second == 0: seconds = '' + else: seconds = u':%02i' % self.second + return u'%02i:%02i%s' % (self.hour, self.minute, seconds) + + def __eq__(self, other): return self._t == self._time(other, False) + def __ne__(self, other): return self._t != self._time(other, False) + def __lt__(self, other): + if other is None: return False + else: return self._t < self._time(other) + def __le__(self, other): + if other is None: return False + else: return self._t <= self._time(other) + def __gt__(self, other): + if other is None: return True + else: return self._t > self._time(other) + def __ge__(self, other): + if other is None: return True + else: return self._t >= self._time(other) + def __cmp__(self, other): + if other is None: return 1 + else: return cmp(self._t, self._time(other)) + def __hash__(self): return hash(self._t) + + def _time(self, t, required=True): + """Retourner l'instance de datetime.time correspondant à l'objet t. + """ + if isinstance(t, pytime): return t + elif isinstance(t, Time): return t._t + elif required: raise ValueError("Expected datetime.time or Time instance, got %s" % repr(t)) + else: return None + + def _s(self, t=None): + """Obtenir le nombre de secondes depuis minuit de t. + """ + if t is None: t = self + if isinstance(t, Time): t = t._t + if isinstance(t, pytime): + return t.hour * 3600 + t.minute * 60 + t.second + elif isinstance(t, timedelta): return t.seconds + elif isnum(t): return t + else: raise ValueError("Expected number, datetime.time, datetime.timedelta or Time instance, got %s" % repr(t)) + + def _new(cls, t=None, s=None): + """Constructeur. t est une instance de Time ou datetime.time. s est un + nombre de secondes depuis minuit. + """ + if t is not None: + if isintance(t, pytime): return cls(t.hour, t.minute, t.second) + elif isinstance(t, Time): return cls(t.hour, t.minute, t.second) + else: raise ValueError("Expected datetime.time or Time instance, got %s" % repr(t)) + elif s is not None: return cls(s=s) + else: return cls() + _new = classmethod(_new) + + def replace(self, hour=None, minute=None, second=None): + """Retourner une nouvelle instance avec les champs spécifiés modifiés. + """ + kw = {} + for name, value in [('hour', hour), ('minute', minute), ('second', second)]: + if value is not None: kw[name] = value + return self._new(self._t.replace(**kw)) + + def __add__(self, other): return self._new(s=self._s() + self._s(other)) + __radd__ = __add__ + def add(self, seconds=1): return self + seconds + + def __sub__(self, other): return self._new(s=self._s() - self._s(other)) + __rsub__ = __sub__ + def sub(self, seconds=1): return self - seconds + + def diff(self, other): return self._s() - self._s(other) + +def istime(t): + """Tester si t est une instance de Time + """ + return isinstance(t, Time) +def isanytime(t): + """Tester si t est une instance de Time ou datetime.time + """ + return isinstance(t, Time) or isinstance(t, pytime) + +RE_TIME = re.compile(r'(\d+)(?:[.:](\d+)(?:[.:](\d+))?)?$') +def parse_time(s): + """Parser une chaine et retourner une instance de Time. + """ + mo = RE_TIME.match(s) + if mo is None: raise ValueError("Invalid time format: %s" % _s(s)) + hour = mo.group(1) + minute = mo.group(2) + second = mo.group(3) + if hour is not None: hour = int(hour) + if minute is not None: minute = int(minute) + if second is not None: second = int(second) + return Time(hour, minute, second) + +def ensure_time(t): + """Retourner une instance de Time, ou None si t==None. + + t peut être une instance de datetime.time, Time ou une chaine. + """ + if t is None: return None + elif isinstance(t, Time): return t + elif isinstance(t, pytime): return Time._new(t) + if not isstr(t): t = _s(t) + return parse_time(t) diff --git a/pyulib/src/ulib/base/times.pyc b/pyulib/src/ulib/base/times.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95ca76ed6211742fae93685c330de5eae7301d9f GIT binary patch literal 12202 zcmd5?OK%+4l|I!?QY6I|X+0=PmgSNZn#~DAYV3^daAZ5SY*|iB+9=94+)+?zb``~v zs;gRct8EF92AH9ctP*59yUbz+&;Z6D2nMsrCV_TY1jvubAIKlbeBZg%)!meASV2pP zsC)aqALo6}tv~*`HvO^t+Yeh({F}z_cacoh5ee|$ksBfn6rPiY(`%_nqoU=5a-%A7 zRT^XBaP*AU$E7iD>ovJCq5TIWo|ML*GIHiT>c=xUPX#gYo3;u296DW3W=TSS% zr%>GdXv4qq-o`tZ@=4S#c>|Tpy`C}jT;T-HjZ8T=GR?%UM1XginF)REJcw$Tm~Lk4 zbznhquEJhhs0v^cPrE~0F%BD?j1+yRswxjrW8f;g;H5+yN<6<(0c4 zH>=`&QVblE>oPF#m2%*#9Jqpkn1@AnDu6rp-i-bkd31pp0TCfdT{9j=f!dCeZf@ag z!d99Dwx5H{>&Vu2({{Lay%jg3WbN~`v(`?7ZXB95OiR<)taWa8<7j)$WUVzVY&T7~ zMmU;PAe;E`ccNRqUHk->J&a78s??lmXSV$PJB1NxXE*I;-W&=~2sRc6XRyJS!G_pl zBHEOGNN1!HN9MPbBsZf4G}!}kq<5sOqA(I%l1mhqgvT-$<#EJmRgw3So{m|rw0Nf? zdlk7_5fJO$isVY$pwXgH*bX+rRUkz`#+HIHYo`h#=0!H^T-qbict@EXLuT7}Ma7Hv z4BL=wHVVQmFH2JXCRSF~7UvlLW}FuH;y<$fTS(@YIBN*esxqF^eVIZ%94Cq>MIj?0 zS5d9>st2@M?NukWI@YUBafPA(<8lv*VOn-4D58S{@^UaK!4!U`B{+zm83}6mIV8a> zejxInk(Z$yxX59-hXY`MmH##F71s1rwVp1(JgW2IIIQ@G;;`Z$isK}Y z+JNG)f**>*3jTTOI(9*6oYumEgeN3;Ue5rHf%CXphSIQiJR|p@5>H9^oP?+JR8S-3 z_0P&ZXvotNoYU2wm+*OMxDtFrf<;~Dj07)e*I5bAO5>b%Eom3V)U|6-ysi2_5L3TE z_EF+~o;5q2FmpQ~W_KxQLI?uWtIU>Gr>9-~do$0&B#+YMjC(Q6-E{jp^3ddNyPG$+ z<8Z}|VmArhon|+8gEVQmou+Z)rEVU@k-^%nwA~KfR{Y?;t`{^D<8~7_h+14VX+~MN zsy!`IdM_yp9Y6z^0U~>=&(v1oiMJ=?MhxJINYNGmFA7S-t9Op`Dt z)VWu%9eWfT-aIoTD=c)h$_W)4eSHoh!9>l?=w+PmM<4>LE3t$x#%+Q5Vn zA*jsxQxrhWuo}o=)5kOORY{(~Qg?omvFf)l}^98a?Y1W3m%u4D| z9opOh(e5^{fksLid%w>;pMl>3RLMB*;-@R_VjC%NZ+0UWeNeP6sPsDM%(&-QTxi&V+7GgW(I{tQ(uf&TQti$Qe72|HTXW9YGZs~ z;~&1CG~1!?_l2Okbt)K@3ay5CB9agUp85U`4^Q(?KsD1&%~B1)#v3WsP*^@L*epR2 zTGe4_dEdfn8Y5X>q&Q`$fCa4qZlqO()}uh;ok#CT`A325Bd7yVT<6K2HYmCzB)xq1 zZw(5K+W6fhYNY{aGI3TRpjB=u!!ir?hPwV>t|333{(;I@8?WP?FEwKm4p2MW z(tJ0}MvGsHZ{H7Z0>>y>EB!Ie$KbCkoQl>`lt6^W)*34p$L2M1c;!389sO^ z&{xm}pa-Ic0>Z~oI9pD^d=T$yM8n7iX>Var4<}@8s}^|;>%H+-PvJr_tgkM%((tLk z>Kb>{{vl2t|5~{1-^sOrr&}Yt*4U%3rPlS8tQ$U6j9ml9emt_PjX(No9)0Mido5t> zU%up8Y|-vKr5n{ypq!WgQ{LRN~@e z9!Svv_ygK0#2s)0bl65!JSiT_&xWn_)%)J8F*zN7G z;$BPP_?S(NRpfLtA~@uBd+ReRC8-u=@5jx!8@`ieX{H#WC8y%tT2J&)yi>>&vRMfC zDGNvY!sAg}`~=Cm9o0FA@j01v4#>&AI4?2xUl>R(!c&%YXF+U*J4;sT84W37L>70$ zod^>Flc#N`MyZZ+7^2}7f$O3k>~YJL)^Ek=f()ie9@7Qp`vimVcvbS5VT!=l_%lQc`>Og1>3%tVkXfzbev74&DlCy4 zVH<;;)_|rxrxvrP)5z`K+sFpAJgH=)W{XER^yf&1@gAwtADu&FWzIRR1fu%>A=ds6 z3wj_BN1VcQlIfFzIjoNO zrQI5CAhrTsp=nPXHKq1wJJAj#w9RM8E+QnDXI*f1H(Pb><3JZL0zH-`K`4W#-C=C6 zB4EguglhkrIvy zGCv7F|99+VLvv#w8^{x;lQ0wyIW1yAs;@#u6<2G z%qMC0voJ=aq$GjnXJHGWlQN9l3-B_EjK_3@dQs}ftNPxq^6qBebnq^7H+4MDprk6| zk%3bx>>Hme92rzlcMBo5Za7jrc|62_B59yQsuRvx1e)P)ck zt*1>3Q$4M!+tCLms@MjH`yc!aJAaP>-i5z8cujZP_kZ=!9SFF6kX?AHkj1%v{m`ih zay=Ef%@XcjuK?VGUduz02vpZSQ4*!RgQ(c~U;HW&Y}Q}k#mYjpM zg=nHiywzGH<#w_ZPu}@${orYzpz%&~z2}*oW5$4j_m9k8WcCuXSD3xZj8DTJtt0OO zv)7ot!R$?D>cY`5RKO?~>z6@7^uGg~k0 z6smooi~=4MEB}Wy7(E}l3r&f^yL9-ZYwxD6S8+Fz$Je==yWcpnmnG=D^0I27JgFN` zV5{FC6*o3#QLbMNzPVg~WBmuK>py&`y!^)U8{beQ6If)D>1$6fuP(0>g80=V>E1U(R(^jimwYDyL^_3j&d9+foCTx~DtO=W? zH;vDNFmr}Gdy8l%Jm8_3y(RUb9nXLIYR>0)nz;mvA*X-H4=ghnt9W7C#fN|)pXZ8K z<)XbbcM6Nw8Z?HY=e!wfsO_sPUlhYwdKOgc~iFP|E%e$xy^fbrzP-Nh>u_nk$nM z$q@I@QiT`*A&@E z?|zpRrz)(x8J1}3d4%kf;&js3ACRnHF-|AcMl22~#_D_~)*zYvP}#6veyFTZLcI@_ zwbJ`gSs(p)bDe8HRMxrWhsuW0n*N8%`r0-4p)$3v>c|MLPZ;=}+b_OUX!2$pL#q>u zS!gx)vncDg6skLW2(V5ZeSJ0kxA7sar%+YhLmul!sRi*Y7a&#I*Kqdj`@&Mc($MEr z2DDn1!#w3MiUmCyK5y$pap--*>BO2x@b=kEOC!)oddmyezIk1#c@_K2fJ^u{QJq0h dVa}Pa%p*a2Lw`?A;;%9_G5-_l>{(~3_W$outPKDF literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/base/tmpfiles.py b/pyulib/src/ulib/base/tmpfiles.py new file mode 100644 index 0000000..60e4060 --- /dev/null +++ b/pyulib/src/ulib/base/tmpfiles.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +import i_need_py23 + +"""Des fonctions pour gérer les fichiers temporaires. Les fichiers temporaires +créés par les fonctions de ce module sont automatiquement supprimés à l'arrêt du +programme. +""" + +__all__ = ('TempFiles', 'mktemp', 'mktempfile', 'litemp', 'cltemp', 'actemp') + +import os, atexit +from os import path + +import tempfile +try: + from tempfile import mkstemp +except ImportError: + def mkstemp(suffix="", prefix=tempfile.template, dir=None, text=False): + absfile = tempfile.mktemp(suffix, prefix, dir) + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + flags |= getattr(os, 'O_NOINHERIT', 0) + flags |= getattr(os, 'O_NOFOLLOW', 0) + if not text: flags |= getattr(os, 'O_BINARY', 0) + return os.open(absfile, flags, 0600), absfile +try: + from threading import local as ThreadingLocal +except ImportError: + class ThreadingLocal(object): pass + + +def _mktemp(suffix="", prefix=tempfile.template, dir=None, text=False): + """Créer un fichier temporaire et retourner (tmpf, tmpfile) où tmpf est un + file object ouvert en lecture/écriture, et tmpfile le chemin vers le fichier + temporaire. + """ + fd, absfile = mkstemp(suffix, prefix, dir, text) + return os.fdopen(fd, 'w+b'), absfile + +class SharedData: + """Une liste global de fichiers temporaires à supprimer + """ + + tmpfiles = None + def _init_tmpfiles(self): + self.tmpfiles = [] + + registered = False + def _register_atexit(self): + if not self.registered: + self.registered = True + atexit.register(self.remove_tmpfiles) + + def __init__(self): + self._init_tmpfiles() + + def add_tmpfile(self, tmpfile): + self.tmpfiles.append(tmpfile) + self._register_atexit() + + def get_last_index(self): + return len(self.tmpfiles) + + def remove_tmpfiles(self, li=None): + li = li or 0 + for tmpfile in self.tmpfiles[li:]: + try: + if path.exists(tmpfile): + os.remove(tmpfile) + except OSError: pass + del self.tmpfiles[li:] + +class ThreadLocalData(ThreadingLocal, SharedData): + """Une liste par thread de fichiers temporaires à supprimer + """ + def __init__(self): + self._init_tmpfiles() + +class TempFiles: + def __init__(self, local=None): + if local is None: local = SharedData() + self.local = local + + def mktemp(self, suffix="", prefix=tempfile.template, dir=None, text=False): + """Créer un fichier temporaire et retourner (tmpf, tmpfile) où tmpf est un + file object ouvert en lecture/écriture, et tmpfile le chemin vers le fichier + temporaire. + + tmpfile est enregistré pour suppression avec cltemp(). + """ + tmpf, tmpfile = _mktemp(suffix, prefix, dir, text) + self.local.add_tmpfile(tmpfile) + return tmpf, tmpfile + + def mktempfile(self, suffix="", prefix=tempfile.template, dir=None, text=False): + """Créer un fichier temporaire et retourner tmpfile, le chemin vers le + fichier temporaire. + + tmpfile est enregistré pour suppression avec cltemp(). + """ + tmpf, tmpfile = self.mktemp(suffix, prefix, dir, text) + tmpf.close() + return tmpfile + + def litemp(self): + """Retourner le nombre de fichiers temporaires enregistrés pour + suppression. + + Cette information peut être utilisée par cltemp() pour indiquer à + partir de quel index il faut supprimer les fichiers temporaires. + """ + return self.local.get_last_index() + + def cltemp(self, li=0): + """Supprimer les fichiers temporaires enregistrés à partir de l'index li. + """ + self.local.remove_tmpfiles(li) + + def actemp(self, tmpfile): + """Enregistrer un fichier pour suppression avec cltemp(). + + @return: la valeur de litemp() avant l'enregistrement du fichier + tmpfile. + """ + li = self.litemp() + self.local.add_tmpfile(path.abspath(tmpfile)) + return li + +TEMP_FILES = TempFiles(ThreadLocalData()) + +def mktemp(suffix="", prefix=tempfile.template, dir=None, text=False): + """Créer un fichier temporaire et l'enregistrer dans la liste des fichiers + temporaires à supprimer pour le thread courant. + """ + return TEMP_FILES.mktemp(suffix, prefix, dir, text) + +def mktempfile(suffix="", prefix=tempfile.template, dir=None, text=False): + """Créer un fichier temporaire et l'enregistrer dans la liste des fichiers + temporaires à supprimer pour le thread courant. + """ + return TEMP_FILES.mktempfile(suffix, prefix, dir, text) + +def litemp(): + """Retourner le nombre de fichiers temporaires à supprimer pour le thread courant. + """ + return TEMP_FILES.litemp() + +def cltemp(li=0): + """Supprimer les fichiers temporaires enregistrés pour le thread courant à + partir de l'index li. + """ + return TEMP_FILES.cltemp(li) + +def actemp(tmpfile): + """Ajouter un nom de fichier à la liste des fichiers à supprimer + automatiquement pour le thread courant. + """ + return TEMP_FILES.actemp(tmpfile) diff --git a/pyulib/src/ulib/base/tmpfiles.pyc b/pyulib/src/ulib/base/tmpfiles.pyc new file mode 100644 index 0000000000000000000000000000000000000000..376f78bce993cc6d8b54e1b28cdf8d86564c1974 GIT binary patch literal 8189 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 children + *y --> *ies + *x --> *xes + *s --> *ses + + sinon, on se contente de rajouter un s + + XXX la règle est un peu plus compliquée que cela. l'implémenter + correctement. http://en.wikipedia.org/wiki/English_plural + """ + if RE_CHILD.match(word): return word + __caseof('ren', word) + elif RE_Y.search(word): return word[:-1] + __caseof('ies', word) + elif RE_X.search(word): return word + __caseof('es', word) + elif RE_S.search(word): return word + __caseof('es', word) + else: return word + __caseof('s', word) + +RE_PLURAL = re.compile(r'%(?:<([^#<>]*))?(?:>([^#<>]*))?#') + +def plural(text, count, format=True): + """Dans text, remplacer des marqueurs de pluralité par la chaine appropriée + en fonction de la valeur de count. + + Un marqueur de pluralité est de la forme %[plur]# + Par défaut, sing == '' et plur == 's' + + Si count > 1, les marqueurs sont remplacés par plur, sinon ils sont + remplacés par sing. + + Si format==True et que text contienne une occurence de %i, le formater avec + text % count. + """ + pos = 0 + while True: + mo = RE_PLURAL.search(text, pos) + if mo is None: break + before = text[:mo.start(0)] + after = text[mo.end(0):] + if count > 1: + plural = mo.group(2) + if plural is None: plural = "s" + else: + plural = mo.group(1) + if plural is None: plural = "" + pos = len(before) + len(plural) + text = before + plural + after + if format and text.find("%i") != -1: + text = text % count + return text + +def __split_maybe(s, sep=None): + if not isseq(s): s = s.split(sep) + return s + +ALL_UPPERCASE_PATTERN = re.compile(r'[A-Z0-9]+$') +UNDERSCORE_PATTERN = re.compile(r'_+') +UPPERCASE_PATTERN = re.compile(r'([A-Z]+)?([^A-Z]*)') + +def splitcc(src, plural=False): + """Spliter des mots écrits en CamelCase. La casse des mots n'est pas + modifiée. + + Si plural==True, utiliser enplural pour mettre au pluriel le dernier mot. + + e.g. + splitcc('camelCase') --> ['camel', 'Case'] + splitcc('URClass') --> ['UR', 'Class'] + """ + src = src.strip() + if not src: return [] + + parts = UPPERCASE_PATTERN.findall(src) + if parts[ - 1] == ('', ''): parts = parts[: - 1] + + dests = [] + for prefix, suffix in parts: + if prefix == "": + dests.append(suffix) + elif len(prefix) == 1: + dests.append(prefix + suffix) + else: + # len(prefix) > 1 + dests.append(prefix[: - 1]) + dests.append(prefix[ - 1] + suffix) + + if dests and plural: dests[-1] = enplural(dests[-1]) + return dests + +def joincc(src, firstcap=False, plural=False): + """Joindre des mots en camelCase. Si les mots sont tout en majuscules, il ne + sont pas modifiés. Sinon, il sont transformés en minuscule, et la première + lettre est capitalisée au besoin. + + Si plural==True, utiliser enplural pour mettre au pluriel le dernier mot. + + e.g. + joincc('hello world') --> 'helloWorld' + joincc(['hello', 'world']) --> 'helloWorld' + joincc('Hello world') --> 'helloWorld' + joincc('Hello World') --> 'helloWorld' + joincc('HELLO WORLD') --> 'HELLOWORLD' + joincc('Hello world', true) --> 'HelloWorld' + joincc('Hello World', true) --> 'HelloWorld' + joincc('HELLO WORLD', true) --> 'HELLOWORLD' + """ + dests = [] + first = True + for s in __split_maybe(src): + if ALL_UPPERCASE_PATTERN.match(s) is None: + if first and not firstcap: + s = s.lower() + else: + s = s.capitalize() + dests.append(s) + first = False + + if dests and plural: dests[-1] = enplural(dests[-1]) + return ''.join(dests) + +def splitus(src, plural=False): + """Splitter des mots écrits séparés par des '_'. La casse des mots n'est pas + modifiée. + + Si plural==True, utiliser enplural pour mettre au pluriel le dernier mot. + + e.g. + splitus('under_score') --> ['under', 'score'] + splitus('UR_CLASS') --> ['UR', 'CLASS'] + """ + src = src.strip() + if not src: return [] + + dests = UNDERSCORE_PATTERN.split(src) + if dests and plural: dests[-1] = enplural(dests[-1]) + return dests + +def joinus(src, plural=False): + """Joindre des mots en les séparant par des '_'. Si les mots sont tout en + majuscules, il ne sont pas modifiés. Sinon, il sont transformés en + minuscule avant d'être séparés par '_'. + + Si plural==True, utiliser enplural pour mettre au pluriel le dernier mot. + + e.g. + joinus('hello world') --> 'hello_world' + joinus(['hello', 'world']) --> 'hello_world' + joinus('Hello world') --> 'hello_world' + joinus('Hello World') --> 'hello_world' + joinus('HELLO WORLD') --> 'HELLO_WORLD' + """ + dests = [] + for s in __split_maybe(src): + if ALL_UPPERCASE_PATTERN.match(s) is None: + s = s.lower() + dests.append(s) + + if dests and plural: dests[-1] = enplural(dests[-1]) + return '_'.join(dests) + + +def s2cc(s, firstcap=False, sep=None, plural=False): + """Transformer une suite de mots séparés par sep en une suite de mots en camelCase. + e.g. s2cc('hello world') --> 'helloWorld' + """ + dests = __split_maybe(s, sep) + if dests and plural: dests[-1] = enplural(dests[-1]) + return joincc(dests, firstcap) + +def cc2s(cc, sep=None, plural=False): + """Transformer une suite de mots en camelCase en une suite de mots séparés par sep. + e.g. cc2s('helloWorld') --> 'hello world' + """ + return (sep or ' ').join(map(string.lower, splitcc(cc, plural))) + +def s2us(s, sep=None, plural=False): + """Transformer une suite de mots séparés par sep en une suite de mots séparés par '_'. + e.g. s2cc('hello world') --> 'hello_world' + """ + dests = __split_maybe(s, sep) + if dests and plural: dests[-1] = enplural(dests[-1]) + return joinus(dests) + +def us2s(us, sep=None, plural=False): + """Transformer une suite de mots séparés par '_' en une suite de mots séparés par sep. + e.g. cc2s('hello_world') --> 'hello world' + """ + return (sep or ' ').join(map(string.lower, splitus(us, plural))) + +def cc2us(cc, plural=False): + """Transformer une suite de mots en camelCase en une suite de mots séparés par '_'. + e.g. cc2s('helloWorld') --> 'hello_world' + """ + return joinus(splitcc(cc, plural)) + +def us2cc(us, firstcap=False, plural=False): + """Transformer une suite de mots séparés par '_' en une suite de mots en camelCase. + e.g. cc2s('hello_world') --> 'helloWorld' + """ + return joincc(splitus(us, plural), firstcap) diff --git a/pyulib/src/ulib/base/words.pyc b/pyulib/src/ulib/base/words.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5726ebd0af2bdd92ad6fedd7003064a6e099c0b GIT binary patch literal 9305 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 E0-)i(%>V!Z literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/__init__.py b/pyulib/src/ulib/ext/__init__.py new file mode 100644 index 0000000..5a00fb6 --- /dev/null +++ b/pyulib/src/ulib/ext/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 diff --git a/pyulib/src/ulib/ext/__init__.pyc b/pyulib/src/ulib/ext/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7d34c1d3fda9574422ce6c472679dd52bdd661f GIT binary patch literal 151 zcmcckiI*!(;Xpt#0~9a -f outfile --quiet + -qfoutfile + --file=outfile -q + --quiet --file outfile + +(All of these result in + options.filename == "outfile" + options.verbose == 0 +...just as you might expect.) + +Even niftier, users can run one of + -h + --help +and Optik will print out a brief summary of your script's options: + + usage: [options] + + options: + -h, --help show this help message and exit + -fFILE, --file=FILE write report to FILE + -q, --quiet don't print status messages to stdout + +That's just a taste of the flexibility Optik gives you in parsing your +command-line. See the documentation included in the package for +details. + + +REQUIREMENTS & INSTALLATION +--------------------------- + +Optik requires Python 2.0 or greater (although this release has only +been tested with Python 2.1, 2.2, and 2.3.) + +Installing Optik is easy; just use the standard incantion for installing +Python modules: + + python setup.py install + + +TESTING +------- + +Optik comes with a fairly extensive test suite. To run the test suite, +execute these commands (before or after installing Optik): + + python setup.py build + python test/test_optik.py + + +DOCUMENTATION +------------- + +Optik comes with several documents: + + * tao.txt philosophy and background; if you're not sure what the + difference between an argument and an option is, read this + + * basic.txt basic information on using Optik in your programs + + * advanced.txt a detailed reference guide to all of Optik's features + + * callbacks.txt how to define callback options and write the callback + functions that go with them + + * extending.txt information on extending Optik (eg. how to add new + actions and types) + +Additionally, the examples/ subdirectory demonstrates various ways to +extend Optik. + + +MAILING LISTS +------------- + +The optik-users@lists.sourceforge.net list exists for general discussion +of Optik. To join or view the list archive, visit + + http://lists.sourceforge.net/lists/listinfo/optik-users + +General questions about Optik should be addressed to the list: + + optik-users@lists.sourceforge.net + +(Currently, you must be subscribed to the list to post. Sorry for the +inconvenience, but it's necessary to keep spam from overwhelming actual +discussion.) + +If you want to follow the bleeding edge of Optik development, you can +join the optik-checkins list: + + http://lists.sourceforge.net/lists/listinfo/optik-checkins + + +AUTHOR, COPYRIGHT, AVAILABILITY +------------------------------- + +The latest version of Optik can be found at + http://optik.sourceforge.net/ + +Optik was written by Greg Ward with +contributions by (in no particular order): + + David Goodger + Matthew Mueller + Terrel Shumway + Johannes Gijsbers + +Copyright (c) 2001-2003 Gregory P. Ward. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pyulib/src/ulib/ext/optik141/__init__.py b/pyulib/src/ulib/ext/optik141/__init__.py new file mode 100644 index 0000000..4d8a91d --- /dev/null +++ b/pyulib/src/ulib/ext/optik141/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""optik + +A powerful, extensible, and easy-to-use command-line parser for Python. + +By Greg Ward + +See http://optik.sourceforge.net/ +""" + +# Copyright (c) 2001-2003 Gregory P. Ward. All rights reserved. +# See the README.txt distributed with Optik for licensing terms. + +__revision__ = "$Id: __init__.py,v 1.1 2007/09/13 05:14:58 jclain Exp $" + +__version__ = "1.4.1" + + +# Re-import these for convenience +from option import Option +from option_parser import * +from help import * +from errors import * + + +# Some day, there might be many Option classes. As of Optik 1.3, the +# preferred way to instantiate Options is indirectly, via make_option(), +# which will become a factory function when there are many Option +# classes. +make_option = Option diff --git a/pyulib/src/ulib/ext/optik141/errors.py b/pyulib/src/ulib/ext/optik141/errors.py new file mode 100644 index 0000000..1efc37b --- /dev/null +++ b/pyulib/src/ulib/ext/optik141/errors.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""optik.errors + +Exception classes used by Optik. +""" + +__revision__ = "$Id: errors.py,v 1.1 2007/09/13 05:14:58 jclain Exp $" + +# Copyright (c) 2001-2003 Gregory P. Ward. All rights reserved. +# See the README.txt distributed with Optik for licensing terms. + +# created 2001/10/17 GPW (from optik.py) + +__all__ = ['OptikError', 'OptionError', 'OptionConflictError', + 'OptionValueError', 'BadOptionError'] + + +class OptikError (Exception): + def __init__ (self, msg): + self.msg = msg + + def __str__ (self): + return self.msg + + +class OptionError (OptikError): + """ + Raised if an Option instance is created with invalid or + inconsistent arguments. + """ + + def __init__ (self, msg, option): + self.msg = msg + self.option_id = str(option) + + def __str__ (self): + if self.option_id: + return "option %s: %s" % (self.option_id, self.msg) + else: + return self.msg + +class OptionConflictError (OptionError): + """ + Raised if conflicting options are added to an OptionParser. + """ + +class OptionValueError (OptikError): + """ + Raised if an invalid option value is encountered on the command + line. + """ + +class BadOptionError (OptikError): + """ + Raised if an invalid or ambiguous option is seen on the command-line. + """ diff --git a/pyulib/src/ulib/ext/optik141/help.py b/pyulib/src/ulib/ext/optik141/help.py new file mode 100644 index 0000000..cf2bb0d --- /dev/null +++ b/pyulib/src/ulib/ext/optik141/help.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +import textwrap + +__revision__ = "$Id: help.py,v 1.1 2007/09/13 05:14:58 jclain Exp $" + +__all__ = ['HelpFormatter', 'IndentedHelpFormatter', 'TitledHelpFormatter'] + + +class HelpFormatter: + + """ + Abstract base class for formatting option help. OptionParser + instances should use one of the HelpFormatter subclasses for + formatting help; by default IndentedHelpFormatter is used. + + Instance attributes: + indent_increment : int + the number of columns to indent per nesting level + max_help_position : int + the maximum starting column for option help text + help_position : int + the calculated starting column for option help text; + initially the same as the maximum + width : int + total number of columns for output + level : int + current indentation level + current_indent : int + current indentation level (in columns) + help_width : int + number of columns available for option help text (calculated) + """ + + def __init__ (self, + indent_increment, + max_help_position, + width, + short_first): + self.indent_increment = indent_increment + self.help_position = self.max_help_position = max_help_position + self.width = width + self.current_indent = 0 + self.level = 0 + self.help_width = width - max_help_position + self.short_first = short_first + + def indent (self): + self.current_indent += self.indent_increment + self.level += 1 + + def dedent (self): + self.current_indent -= self.indent_increment + assert self.current_indent >= 0, "Indent decreased below 0." + self.level -= 1 + + def format_usage (self, usage): + raise NotImplementedError, "subclasses must implement" + + def format_heading (self, heading): + raise NotImplementedError, "subclasses must implement" + + def format_description (self, description): + desc_width = self.width - self.current_indent + indent = " "*self.current_indent + return textwrap.fill(description, desc_width, + initial_indent=indent, + subsequent_indent=indent) + + def format_option (self, option): + # The help for each option consists of two parts: + # * the opt strings and metavars + # eg. ("-x", or "-fFILENAME, --file=FILENAME") + # * the user-supplied help string + # eg. ("turn on expert mode", "read data from FILENAME") + # + # If possible, we write both of these on the same line: + # -x turn on expert mode + # + # But if the opt string list is too long, we put the help + # string on a second line, indented to the same column it would + # start in if it fit on the first line. + # -fFILENAME, --file=FILENAME + # read data from FILENAME + result = [] + opts = option.option_strings + opt_width = self.help_position - self.current_indent - 2 + if len(opts) > opt_width: + opts = "%*s%s\n" % (self.current_indent, "", opts) + indent_first = self.help_position + else: # start help on same line as opts + opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) + indent_first = 0 + result.append(opts) + if option.help: + help_lines = textwrap.wrap(option.help, self.help_width) + result.append("%*s%s\n" % (indent_first, "", help_lines[0])) + result.extend(["%*s%s\n" % (self.help_position, "", line) + for line in help_lines[1:]]) + elif opts[-1] != "\n": + result.append("\n") + return "".join(result) + + def store_option_strings (self, parser): + self.indent() + max_len = 0 + for opt in parser.option_list: + strings = self.format_option_strings(opt) + opt.option_strings = strings + max_len = max(max_len, len(strings) + self.current_indent) + self.indent() + for group in parser.option_groups: + for opt in group.option_list: + strings = self.format_option_strings(opt) + opt.option_strings = strings + max_len = max(max_len, len(strings) + self.current_indent) + self.dedent() + self.dedent() + self.help_position = min(max_len + 2, self.max_help_position) + + def format_option_strings (self, option): + """Return a comma-separated list of option strings & metavariables.""" + if option.takes_value(): + metavar = option.metavar or option.dest.upper() + short_opts = [sopt + metavar for sopt in option._short_opts] + long_opts = [lopt + "=" + metavar for lopt in option._long_opts] + else: + short_opts = option._short_opts + long_opts = option._long_opts + + if self.short_first: + opts = short_opts + long_opts + else: + opts = long_opts + short_opts + + return ", ".join(opts) + +class IndentedHelpFormatter (HelpFormatter): + """Format help with indented section bodies. + """ + + def __init__ (self, + indent_increment=2, + max_help_position=24, + width=80, + short_first=1): + HelpFormatter.__init__( + self, indent_increment, max_help_position, width, short_first) + + def format_usage (self, usage): + return "usage: %s\n" % usage + + def format_heading (self, heading): + return "%*s%s:\n" % (self.current_indent, "", heading) + + +class TitledHelpFormatter (HelpFormatter): + """Format help with underlined section headers. + """ + + def __init__ (self, + indent_increment=0, + max_help_position=24, + width=80, + short_first=0): + HelpFormatter.__init__ ( + self, indent_increment, max_help_position, width, short_first) + + def format_usage (self, usage): + return "%s %s\n" % (self.format_heading("Usage"), usage) + + def format_heading (self, heading): + return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading)) diff --git a/pyulib/src/ulib/ext/optik141/option.py b/pyulib/src/ulib/ext/optik141/option.py new file mode 100644 index 0000000..4ba8038 --- /dev/null +++ b/pyulib/src/ulib/ext/optik141/option.py @@ -0,0 +1,386 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""optik.option + +Defines the Option class and some standard value-checking functions. +""" + +__revision__ = "$Id: option.py,v 1.1 2007/09/13 05:14:58 jclain Exp $" + +# Copyright (c) 2001-2003 Gregory P. Ward. All rights reserved. +# See the README.txt distributed with Optik for licensing terms. + +# created 2001/10/17, GPW (from optik.py) + +import sys +import types +from errors import OptionError, OptionValueError + +# Do the right thing with boolean values for all known Python versions. +try: + True, False +except NameError: + (True, False) = (1, 0) + +_builtin_cvt = { "int" : (int, "integer"), + "long" : (long, "long integer"), + "float" : (float, "floating-point"), + "complex" : (complex, "complex") } + +def check_builtin (option, opt, value): + (cvt, what) = _builtin_cvt[option.type] + try: + return cvt(value) + except ValueError: + raise OptionValueError( + #"%s: invalid %s argument %r" % (opt, what, value)) + "option %s: invalid %s value: %r" % (opt, what, value)) + +def check_choice(option, opt, value): + if value in option.choices: + return value + else: + choices = ", ".join(map(repr, option.choices)) + raise OptionValueError( + "option %s: invalid choice: %r (choose from %s)" + % (opt, value, choices)) + +# Not supplying a default is different from a default of None, +# so we need an explicit "not supplied" value. +NO_DEFAULT = "NO"+"DEFAULT" + + +class Option: + """ + Instance attributes: + _short_opts : [string] + _long_opts : [string] + + action : string + type : string + dest : string + default : any + nargs : int + const : any + choices : [string] + callback : function + callback_args : (any*) + callback_kwargs : { string : any } + help : string + metavar : string + """ + + # The list of instance attributes that may be set through + # keyword args to the constructor. + ATTRS = ['action', + 'type', + 'dest', + 'default', + 'nargs', + 'const', + 'choices', + 'callback', + 'callback_args', + 'callback_kwargs', + 'help', + 'metavar'] + + # The set of actions allowed by option parsers. Explicitly listed + # here so the constructor can validate its arguments. + ACTIONS = ("store", + "store_const", + "store_true", + "store_false", + "append", + "count", + "callback", + "help", + "version") + + # The set of actions that involve storing a value somewhere; + # also listed just for constructor argument validation. (If + # the action is one of these, there must be a destination.) + STORE_ACTIONS = ("store", + "store_const", + "store_true", + "store_false", + "append", + "count") + + # The set of actions for which it makes sense to supply a value + # type, ie. where we expect an argument to this option. + TYPED_ACTIONS = ("store", + "append", + "callback") + + # The set of known types for option parsers. Again, listed here for + # constructor argument validation. + TYPES = ("string", "int", "long", "float", "complex", "choice") + + # Dictionary of argument checking functions, which convert and + # validate option arguments according to the option type. + # + # Signature of checking functions is: + # check(option : Option, opt : string, value : string) -> any + # where + # option is the Option instance calling the checker + # opt is the actual option seen on the command-line + # (eg. "-a", "--file") + # value is the option argument seen on the command-line + # + # The return value should be in the appropriate Python type + # for option.type -- eg. an integer if option.type == "int". + # + # If no checker is defined for a type, arguments will be + # unchecked and remain strings. + TYPE_CHECKER = { "int" : check_builtin, + "long" : check_builtin, + "float" : check_builtin, + "complex" : check_builtin, + "choice" : check_choice, + } + + + # CHECK_METHODS is a list of unbound method objects; they are called + # by the constructor, in order, after all attributes are + # initialized. The list is created and filled in later, after all + # the methods are actually defined. (I just put it here because I + # like to define and document all class attributes in the same + # place.) Subclasses that add another _check_*() method should + # define their own CHECK_METHODS list that adds their check method + # to those from this class. + CHECK_METHODS = None + + + # -- Constructor/initialization methods ---------------------------- + + def __init__ (self, *opts, **attrs): + # Set _short_opts, _long_opts attrs from 'opts' tuple. + # Have to be set now, in case no option strings are supplied. + self._short_opts = [] + self._long_opts = [] + opts = self._check_opt_strings(opts) + self._set_opt_strings(opts) + + # Set all other attrs (action, type, etc.) from 'attrs' dict + self._set_attrs(attrs) + + # Check all the attributes we just set. There are lots of + # complicated interdependencies, but luckily they can be farmed + # out to the _check_*() methods listed in CHECK_METHODS -- which + # could be handy for subclasses! The one thing these all share + # is that they raise OptionError if they discover a problem. + for checker in self.CHECK_METHODS: + checker(self) + + def _check_opt_strings (self, opts): + # Filter out None because early versions of Optik had exactly + # one short option and one long option, either of which + # could be None. + opts = filter(None, opts) + if not opts: + raise TypeError("at least one option string must be supplied") + return opts + + def _set_opt_strings (self, opts): + for opt in opts: + if len(opt) < 2: + raise OptionError( + "invalid option string %r: " + "must be at least two characters long" % opt, self) + elif len(opt) == 2: + if not (opt[0] == "-" and opt[1] != "-"): + raise OptionError( + "invalid short option string %r: " + "must be of the form -x, (x any non-dash char)" % opt, + self) + self._short_opts.append(opt) + else: + if not (opt[0:2] == "--" and opt[2] != "-"): + raise OptionError( + "invalid long option string %r: " + "must start with --, followed by non-dash" % opt, + self) + self._long_opts.append(opt) + + def _set_attrs (self, attrs): + for attr in self.ATTRS: + if attrs.has_key(attr): + setattr(self, attr, attrs[attr]) + del attrs[attr] + else: + if attr == 'default': + setattr(self, attr, NO_DEFAULT) + else: + setattr(self, attr, None) + if attrs: + raise OptionError( + "invalid keyword arguments: %s" % ", ".join(attrs.keys()), + self) + + + # -- Constructor validation methods -------------------------------- + + def _check_action (self): + if self.action is None: + self.action = "store" + elif self.action not in self.ACTIONS: + raise OptionError("invalid action: %r" % self.action, self) + + def _check_type (self): + if self.type is None: + # XXX should factor out another class attr here: list of + # actions that *require* a type + if self.action in ("store", "append"): + if self.choices is not None: + # The "choices" attribute implies "choice" type. + self.type = "choice" + else: + # No type given? "string" is the most sensible default. + self.type = "string" + else: + if self.type not in self.TYPES: + raise OptionError("invalid option type: %r" % self.type, self) + if self.action not in self.TYPED_ACTIONS: + raise OptionError( + "must not supply a type for action %r" % self.action, self) + + def _check_choice(self): + if self.type == "choice": + if self.choices is None: + raise OptionError( + "must supply a list of choices for type 'choice'", self) + elif type(self.choices) not in (types.TupleType, types.ListType): + raise OptionError( + "choices must be a list of strings ('%s' supplied)" + % str(type(self.choices)).split("'")[1], self) + elif self.choices is not None: + raise OptionError( + "must not supply choices for type %r" % self.type, self) + + def _check_dest (self): + if self.action in self.STORE_ACTIONS and self.dest is None: + # No destination given, and we need one for this action. + # Glean a destination from the first long option string, + # or from the first short option string if no long options. + if self._long_opts: + # eg. "--foo-bar" -> "foo_bar" + self.dest = self._long_opts[0][2:].replace('-', '_') + else: + self.dest = self._short_opts[0][1] + + def _check_const (self): + if self.action != "store_const" and self.const is not None: + raise OptionError( + "'const' must not be supplied for action %r" % self.action, + self) + + def _check_nargs (self): + if self.action in self.TYPED_ACTIONS: + if self.nargs is None: + self.nargs = 1 + elif self.nargs is not None: + raise OptionError( + "'nargs' must not be supplied for action %r" % self.action, + self) + + def _check_callback (self): + if self.action == "callback": + if not callable(self.callback): + raise OptionError( + "callback not callable: %r" % self.callback, self) + if (self.callback_args is not None and + type(self.callback_args) is not types.TupleType): + raise OptionError( + "callback_args, if supplied, must be a tuple: not %r" + % self.callback_args, self) + if (self.callback_kwargs is not None and + type(self.callback_kwargs) is not types.DictType): + raise OptionError( + "callback_kwargs, if supplied, must be a dict: not %r" + % self.callback_kwargs, self) + else: + if self.callback is not None: + raise OptionError( + "callback supplied (%r) for non-callback option" + % self.callback, self) + if self.callback_args is not None: + raise OptionError( + "callback_args supplied for non-callback option", self) + if self.callback_kwargs is not None: + raise OptionError( + "callback_kwargs supplied for non-callback option", self) + + + CHECK_METHODS = [_check_action, + _check_type, + _check_choice, + _check_dest, + _check_const, + _check_nargs, + _check_callback] + + + # -- Miscellaneous methods ----------------------------------------- + + def __str__ (self): + return "/".join(self._short_opts + self._long_opts) + + def takes_value (self): + return self.type is not None + + + # -- Processing methods -------------------------------------------- + + def check_value (self, opt, value): + checker = self.TYPE_CHECKER.get(self.type) + if checker is None: + return value + else: + return checker(self, opt, value) + + def process (self, opt, value, values, parser): + + # First, convert the value(s) to the right type. Howl if any + # value(s) are bogus. + if value is not None: + if self.nargs == 1: + value = self.check_value(opt, value) + else: + value = tuple([self.check_value(opt, v) for v in value]) + + # And then take whatever action is expected of us. + # This is a separate method to make life easier for + # subclasses to add new actions. + return self.take_action( + self.action, self.dest, opt, value, values, parser) + + def take_action (self, action, dest, opt, value, values, parser): + if action == "store": + setattr(values, dest, value) + elif action == "store_const": + setattr(values, dest, self.const) + elif action == "store_true": + setattr(values, dest, True) + elif action == "store_false": + setattr(values, dest, False) + elif action == "append": + values.ensure_value(dest, []).append(value) + elif action == "count": + setattr(values, dest, values.ensure_value(dest, 0) + 1) + elif action == "callback": + args = self.callback_args or () + kwargs = self.callback_kwargs or {} + self.callback(self, opt, value, parser, *args, **kwargs) + elif action == "help": + parser.print_help() + sys.exit(0) + elif action == "version": + parser.print_version() + sys.exit(0) + else: + raise RuntimeError, "unknown action %r" % self.action + + return 1 + +# class Option diff --git a/pyulib/src/ulib/ext/optik141/option_parser.py b/pyulib/src/ulib/ext/optik141/option_parser.py new file mode 100644 index 0000000..5f4ec15 --- /dev/null +++ b/pyulib/src/ulib/ext/optik141/option_parser.py @@ -0,0 +1,771 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +"""optik.option_parser + +Provides the OptionParser and Values classes. +""" + +__revision__ = "$Id: option_parser.py,v 1.1 2007/09/13 05:14:58 jclain Exp $" + +# Copyright (c) 2001-2003 Gregory P. Ward. All rights reserved. +# See the README.txt distributed with Optik for licensing terms. + +# created 2001/10/17, GPW (from optik.py) + +import sys, os +import types +from option import Option, NO_DEFAULT +from help import IndentedHelpFormatter +from errors import OptionConflictError, OptionValueError, BadOptionError + +__all__ = ['SUPPRESS_HELP', 'SUPPRESS_USAGE', + 'STD_HELP_OPTION', 'STD_VERSION_OPTION', + 'Values', 'OptionContainer', 'OptionGroup', 'OptionParser'] + +def get_prog_name (): + return os.path.basename(sys.argv[0]) + + +SUPPRESS_HELP = "SUPPRESS"+"HELP" +SUPPRESS_USAGE = "SUPPRESS"+"USAGE" + +STD_HELP_OPTION = Option("-h", "--help", + action="help", + help="show this help message and exit") +STD_VERSION_OPTION = Option("--version", + action="version", + help="show program's version number and exit") + + +class Values: + + def __init__ (self, defaults=None): + if defaults: + for (attr, val) in defaults.items(): + setattr(self, attr, val) + + def __repr__ (self): + return ("<%s at 0x%x: %r>" + % (self.__class__.__name__, id(self), self.__dict__)) + + def _update_careful (self, dict): + """ + Update the option values from an arbitrary dictionary, but only + use keys from dict that already have a corresponding attribute + in self. Any keys in dict without a corresponding attribute + are silently ignored. + """ + for attr in dir(self): + if dict.has_key(attr): + dval = dict[attr] + if dval is not None: + setattr(self, attr, dval) + + def _update_loose (self, dict): + """ + Update the option values from an arbitrary dictionary, + using all keys from the dictionary regardless of whether + they have a corresponding attribute in self or not. + """ + self.__dict__.update(dict) + + def _update (self, dict, mode): + if mode == "careful": + self._update_careful(dict) + elif mode == "loose": + self._update_loose(dict) + else: + raise ValueError, "invalid update mode: %r" % mode + + def read_module (self, modname, mode="careful"): + __import__(modname) + mod = sys.modules[modname] + self._update(vars(mod), mode) + + def read_file (self, filename, mode="careful"): + vars = {} + execfile(filename, vars) + self._update(vars, mode) + + def ensure_value (self, attr, value): + if not hasattr(self, attr) or getattr(self, attr) is None: + setattr(self, attr, value) + return getattr(self, attr) + + +class OptionContainer: + + """ + Abstract base class. + + Class attributes: + standard_option_list : [Option] + list of standard options that will be accepted by all instances + of this parser class (intended to be overridden by subclasses). + + Instance attributes: + option_list : [Option] + the list of Option objects contained by this OptionContainer + _short_opt : { string : Option } + dictionary mapping short option strings, eg. "-f" or "-X", + to the Option instances that implement them. If an Option + has multiple short option strings, it will appears in this + dictionary multiple times. [1] + _long_opt : { string : Option } + dictionary mapping long option strings, eg. "--file" or + "--exclude", to the Option instances that implement them. + Again, a given Option can occur multiple times in this + dictionary. [1] + defaults : { string : any } + dictionary mapping option destination names to default + values for each destination [1] + + [1] These mappings are common to (shared by) all components of the + controlling OptionParser, where they are initially created. + + """ + + def __init__ (self, option_class, conflict_handler, description): + # Initialize the option list and related data structures. + # This method must be provided by subclasses, and it must + # initialize at least the following instance attributes: + # option_list, _short_opt, _long_opt, defaults. + self._create_option_list() + + self.option_class = option_class + self.set_conflict_handler(conflict_handler) + self.set_description(description) + + def _create_option_mappings (self): + # For use by OptionParser constructor -- create the master + # option mappings used by this OptionParser and all + # OptionGroups that it owns. + self._short_opt = {} # single letter -> Option instance + self._long_opt = {} # long option -> Option instance + self.defaults = {} # maps option dest -> default value + + + def _share_option_mappings (self, parser): + # For use by OptionGroup constructor -- use shared option + # mappings from the OptionParser that owns this OptionGroup. + self._short_opt = parser._short_opt + self._long_opt = parser._long_opt + self.defaults = parser.defaults + + def set_conflict_handler (self, handler): + if handler not in ("ignore", "error", "resolve"): + raise ValueError, "invalid conflict_resolution value %r" % handler + self.conflict_handler = handler + + def set_description (self, description): + self.description = description + + + # -- Option-adding methods ----------------------------------------- + + def _check_conflict (self, option): + conflict_opts = [] + for opt in option._short_opts: + if self._short_opt.has_key(opt): + conflict_opts.append((opt, self._short_opt[opt])) + for opt in option._long_opts: + if self._long_opt.has_key(opt): + conflict_opts.append((opt, self._long_opt[opt])) + + if conflict_opts: + handler = self.conflict_handler + if handler == "ignore": # behaviour for Optik 1.0, 1.1 + pass + elif handler == "error": # new in 1.2 + raise OptionConflictError( + "conflicting option string(s): %s" + % ", ".join([co[0] for co in conflict_opts]), + option) + elif handler == "resolve": # new in 1.2 + for (opt, c_option) in conflict_opts: + if opt.startswith("--"): + c_option._long_opts.remove(opt) + del self._long_opt[opt] + else: + c_option._short_opts.remove(opt) + del self._short_opt[opt] + if not (c_option._short_opts or c_option._long_opts): + c_option.container.option_list.remove(c_option) + + def add_option (self, *args, **kwargs): + """add_option(Option) + add_option(opt_str, ..., kwarg=val, ...) + """ + if type(args[0]) is types.StringType: + option = self.option_class(*args, **kwargs) + elif len(args) == 1 and not kwargs: + option = args[0] + if not isinstance(option, Option): + raise TypeError, "not an Option instance: %r" % option + else: + raise TypeError, "invalid arguments" + + self._check_conflict(option) + + self.option_list.append(option) + option.container = self + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + if option.dest is not None: # option has a dest, we need a default + if option.default is not NO_DEFAULT: + self.defaults[option.dest] = option.default + elif not self.defaults.has_key(option.dest): + self.defaults[option.dest] = None + + return option + + def add_options (self, option_list): + for option in option_list: + self.add_option(option) + + # -- Option query/removal methods ---------------------------------- + + def get_option (self, opt_str): + return (self._short_opt.get(opt_str) or + self._long_opt.get(opt_str)) + + def has_option (self, opt_str): + return (self._short_opt.has_key(opt_str) or + self._long_opt.has_key(opt_str)) + + def remove_option (self, opt_str): + option = self._short_opt.get(opt_str) + if option is None: + option = self._long_opt.get(opt_str) + if option is None: + raise ValueError("no such option %r" % opt_str) + + for opt in option._short_opts: + del self._short_opt[opt] + for opt in option._long_opts: + del self._long_opt[opt] + option.container.option_list.remove(option) + + + # -- Help-formatting methods --------------------------------------- + + def format_option_help (self, formatter): + if not self.option_list: + return "" + result = [] + for option in self.option_list: + if not option.help is SUPPRESS_HELP: + result.append(formatter.format_option(option)) + return "".join(result) + + def format_description (self, formatter): + if self.description: + return formatter.format_description(self.description) + else: + return "" + + def format_help (self, formatter): + if self.description: + desc = self.format_description(formatter) + "\n" + else: + desc = "" + return desc + self.format_option_help(formatter) + + +class OptionGroup (OptionContainer): + + def __init__ (self, parser, title, description=None): + self.parser = parser + OptionContainer.__init__( + self, parser.option_class, parser.conflict_handler, description) + self.title = title + + def _create_option_list (self): + self.option_list = [] + self._share_option_mappings(self.parser) + + def set_title (self, title): + self.title = title + + # -- Help-formatting methods --------------------------------------- + + def format_help (self, formatter): + result = formatter.format_heading(self.title) + formatter.indent() + result += OptionContainer.format_help(self, formatter) + formatter.dedent() + return result + + +class OptionParser (OptionContainer): + + """ + Class attributes: + standard_option_list : [Option] + list of standard options that will be accepted by all instances + of this parser class (intended to be overridden by subclasses). + + Instance attributes: + usage : string + a usage string for your program. Before it is displayed + to the user, "%prog" will be expanded to the name of + your program (self.prog or os.path.basename(sys.argv[0])). + prog : string + the name of the current program (to override + os.path.basename(sys.argv[0])). + + allow_interspersed_args : boolean = true + if true, positional arguments may be interspersed with options. + Assuming -a and -b each take a single argument, the command-line + -ablah foo bar -bboo baz + will be interpreted the same as + -ablah -bboo -- foo bar baz + If this flag were false, that command line would be interpreted as + -ablah -- foo bar -bboo baz + -- ie. we stop processing options as soon as we see the first + non-option argument. (This is the tradition followed by + Python's getopt module, Perl's Getopt::Std, and other argument- + parsing libraries, but it is generally annoying to users.) + + rargs : [string] + the argument list currently being parsed. Only set when + parse_args() is active, and continually trimmed down as + we consume arguments. Mainly there for the benefit of + callback options. + largs : [string] + the list of leftover arguments that we have skipped while + parsing options. If allow_interspersed_args is false, this + list is always empty. + values : Values + the set of option values currently being accumulated. Only + set when parse_args() is active. Also mainly for callbacks. + + Because of the 'rargs', 'largs', and 'values' attributes, + OptionParser is not thread-safe. If, for some perverse reason, you + need to parse command-line arguments simultaneously in different + threads, use different OptionParser instances. + + """ + + standard_option_list = [] + + def __init__ (self, + usage=None, + option_list=None, + option_class=Option, + version=None, + conflict_handler="error", + description=None, + formatter=None, + add_help_option=1, + prog=None): + OptionContainer.__init__( + self, option_class, conflict_handler, description) + self.set_usage(usage) + self.prog = prog + self.version = version + self.allow_interspersed_args = 1 + if formatter is None: + formatter = IndentedHelpFormatter() + self.formatter = formatter + + # Populate the option list; initial sources are the + # standard_option_list class attribute, the 'option_list' + # argument, and the STD_VERSION_OPTION (if 'version' supplied) + # and STD_HELP_OPTION globals. + self._populate_option_list(option_list, + add_help=add_help_option) + + self._init_parsing_state() + + # -- Private methods ----------------------------------------------- + # (used by our or OptionContainer's constructor) + + def _create_option_list (self): + self.option_list = [] + self.option_groups = [] + self._create_option_mappings() + + def _populate_option_list (self, option_list, add_help=1): + if self.standard_option_list: + self.add_options(self.standard_option_list) + if option_list: + self.add_options(option_list) + if self.version: + self.add_option(STD_VERSION_OPTION) + if add_help: + self.add_option(STD_HELP_OPTION) + + def _init_parsing_state (self): + # These are set in parse_args() for the convenience of callbacks. + self.rargs = None + self.largs = None + self.values = None + + + # -- Simple modifier methods --------------------------------------- + + def set_usage (self, usage): + if usage is None: + self.usage = "%prog [options]" + elif usage is SUPPRESS_USAGE: + self.usage = None + elif usage.startswith("usage: "): + # for backwards compatibility with Optik 1.3 and earlier + self.usage = usage[7:] + else: + self.usage = usage + + def enable_interspersed_args (self): + self.allow_interspersed_args = 1 + + def disable_interspersed_args (self): + self.allow_interspersed_args = 0 + + def set_default (self, dest, value): + self.defaults[dest] = value + + def set_defaults (self, **kwargs): + self.defaults.update(kwargs) + + def get_default_values (self): + return Values(self.defaults) + + + # -- OptionGroup methods ------------------------------------------- + + def add_option_group (self, *args, **kwargs): + # XXX lots of overlap with OptionContainer.add_option() + if type(args[0]) is types.StringType: + group = OptionGroup(self, *args, **kwargs) + elif len(args) == 1 and not kwargs: + group = args[0] + if not isinstance(group, OptionGroup): + raise TypeError, "not an OptionGroup instance: %r" % group + if group.parser is not self: + raise ValueError, "invalid OptionGroup (wrong parser)" + else: + raise TypeError, "invalid arguments" + + self.option_groups.append(group) + return group + + def get_option_group (self, opt_str): + option = (self._short_opt.get(opt_str) or + self._long_opt.get(opt_str)) + if option and option.container is not self: + return option.container + return None + + + # -- Option-parsing methods ---------------------------------------- + + def _get_args (self, args): + if args is None: + return sys.argv[1:] + else: + return args[:] # don't modify caller's list + + def parse_args (self, args=None, values=None): + """ + parse_args(args : [string] = sys.argv[1:], + values : Values = None) + -> (values : Values, args : [string]) + + Parse the command-line options found in 'args' (default: + sys.argv[1:]). Any errors result in a call to 'error()', which + by default prints the usage message to stderr and calls + sys.exit() with an error message. On success returns a pair + (values, args) where 'values' is an Values instance (with all + your option values) and 'args' is the list of arguments left + over after parsing options. + """ + rargs = self._get_args(args) + if values is None: + values = self.get_default_values() + + # Store the halves of the argument list as attributes for the + # convenience of callbacks: + # rargs + # the rest of the command-line (the "r" stands for + # "remaining" or "right-hand") + # largs + # the leftover arguments -- ie. what's left after removing + # options and their arguments (the "l" stands for "leftover" + # or "left-hand") + self.rargs = rargs + self.largs = largs = [] + self.values = values + + try: + _stop = self._process_args(largs, rargs, values) + except (BadOptionError, OptionValueError), err: + self.error(err.msg) + + args = largs + rargs + return self.check_values(values, args) + + def check_values (self, values, args): + """ + check_values(values : Values, args : [string]) + -> (values : Values, args : [string]) + + Check that the supplied option values and leftover arguments are + valid. Returns the option values and leftover arguments + (possibly adjusted, possibly completely new -- whatever you + like). Default implementation just returns the passed-in + values; subclasses may override as desired. + """ + return (values, args) + + def _process_args (self, largs, rargs, values): + """_process_args(largs : [string], + rargs : [string], + values : Values) + + Process command-line arguments and populate 'values', consuming + options and arguments from 'rargs'. If 'allow_interspersed_args' is + false, stop at the first non-option argument. If true, accumulate any + interspersed non-option arguments in 'largs'. + """ + while rargs: + arg = rargs[0] + # We handle bare "--" explicitly, and bare "-" is handled by the + # standard arg handler since the short arg case ensures that the + # len of the opt string is greater than 1. + if arg == "--": + del rargs[0] + return + elif arg[0:2] == "--": + # process a single long option (possibly with value(s)) + self._process_long_opt(rargs, values) + elif arg[:1] == "-" and len(arg) > 1: + # process a cluster of short options (possibly with + # value(s) for the last one only) + self._process_short_opts(rargs, values) + elif self.allow_interspersed_args: + largs.append(arg) + del rargs[0] + else: + return # stop now, leave this arg in rargs + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt (self, opt): + """_match_long_opt(opt : string) -> string + + Determine which long option string 'opt' matches, ie. which one + it is an unambiguous abbrevation for. Raises BadOptionError if + 'opt' doesn't unambiguously match any long option string. + """ + return _match_abbrev(opt, self._long_opt) + + def _process_long_opt (self, rargs, values): + arg = rargs.pop(0) + + # Value explicitly attached to arg? Pretend it's the next + # argument. + if "=" in arg: + (opt, next_arg) = arg.split("=", 1) + rargs.insert(0, next_arg) + had_explicit_value = 1 + else: + opt = arg + had_explicit_value = 0 + + opt = self._match_long_opt(opt) + option = self._long_opt[opt] + if option.takes_value(): + nargs = option.nargs + if len(rargs) < nargs: + if nargs == 1: + self.error("%s option requires a value" % opt) + else: + self.error("%s option requires %d values" + % (opt, nargs)) + elif nargs == 1: + value = rargs.pop(0) + else: + value = tuple(rargs[0:nargs]) + del rargs[0:nargs] + + elif had_explicit_value: + self.error("%s option does not take a value" % opt) + + else: + value = None + + option.process(opt, value, values, self) + + def _process_short_opts (self, rargs, values): + arg = rargs.pop(0) + stop = 0 + i = 1 + for ch in arg[1:]: + opt = "-" + ch + option = self._short_opt.get(opt) + i += 1 # we have consumed a character + + if not option: + self.error("no such option: %s" % opt) + if option.takes_value(): + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + rargs.insert(0, arg[i:]) + stop = 1 + + nargs = option.nargs + if len(rargs) < nargs: + if nargs == 1: + self.error("%s option requires a value" % opt) + else: + self.error("%s option requires %s values" + % (opt, nargs)) + elif nargs == 1: + value = rargs.pop(0) + else: + value = tuple(rargs[0:nargs]) + del rargs[0:nargs] + + else: # option doesn't take a value + value = None + + option.process(opt, value, values, self) + + if stop: + break + + + # -- Feedback methods ---------------------------------------------- + + def error (self, msg): + """error(msg : string) + + Print a usage message incorporating 'msg' to stderr and exit. + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(sys.stderr) + sys.exit("%s: error: %s" % (get_prog_name(), msg)) + + def get_usage (self): + if self.usage: + return self.formatter.format_usage( + self.usage.replace("%prog", get_prog_name())) + else: + return "" + + def print_usage (self, file=None): + """print_usage(file : file = stdout) + + Print the usage message for the current program (self.usage) to + 'file' (default stdout). Any occurence of the string "%prog" in + self.usage is replaced with the name of the current program + (basename of sys.argv[0]). Does nothing if self.usage is empty + or not defined. + """ + if self.usage: + print >>file, self.get_usage() + + def get_version (self): + if self.version: + return self.version.replace("%prog", get_prog_name()) + else: + return "" + + def print_version (self, file=None): + """print_version(file : file = stdout) + + Print the version message for this program (self.version) to + 'file' (default stdout). As with print_usage(), any occurence + of "%prog" in self.version is replaced by the current program's + name. Does nothing if self.version is empty or undefined. + """ + if self.version: + print >>file, self.get_version() + + def format_option_help (self, formatter=None): + if formatter is None: + formatter = self.formatter + formatter.store_option_strings(self) + result = [] + result.append(formatter.format_heading("options")) + formatter.indent() + if self.option_list: + result.append(OptionContainer.format_option_help(self, formatter)) + result.append("\n") + for group in self.option_groups: + result.append(group.format_help(formatter)) + result.append("\n") + formatter.dedent() + # Drop the last "\n", or the header if no options or option groups: + return "".join(result[:-1]) + + def format_help (self, formatter=None): + if formatter is None: + formatter = self.formatter + result = [] + if self.usage: + result.append(self.get_usage() + "\n") + if self.description: + result.append(self.format_description(formatter) + "\n") + result.append(self.format_option_help(formatter)) + return "".join(result) + + def print_help (self, file=None): + """print_help(file : file = stdout) + + Print an extended help message, listing all options and any + help text provided with them, to 'file' (default stdout). + """ + if file is None: + file = sys.stdout + file.write(self.format_help()) + +# class OptionParser + + +def _match_abbrev (s, wordmap): + """_match_abbrev(s : string, wordmap : {string : Option}) -> string + + Return the string key in 'wordmap' for which 's' is an unambiguous + abbreviation. If 's' is found to be ambiguous or doesn't match any of + 'words', raise BadOptionError. + """ + # Is there an exact match? + if wordmap.has_key(s): + return s + else: + # Isolate all words with s as a prefix. + possibilities = [word for word in wordmap.keys() + if word.startswith(s)] + # No exact match, so there had better be just one possibility. + if len(possibilities) == 1: + return possibilities[0] + elif not possibilities: + raise BadOptionError("no such option: %s" % s) + else: + # More than one possible completion: ambiguous prefix. + raise BadOptionError("ambiguous option: %s (%s?)" + % (s, ", ".join(possibilities))) diff --git a/pyulib/src/ulib/ext/optik141/textwrap.py b/pyulib/src/ulib/ext/optik141/textwrap.py new file mode 100644 index 0000000..5610a6c --- /dev/null +++ b/pyulib/src/ulib/ext/optik141/textwrap.py @@ -0,0 +1,291 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""Text wrapping and filling. +""" + +# Copyright (C) 1999-2001 Gregory P. Ward. +# Copyright (C) 2002, 2003 Python Software Foundation. +# Written by Greg Ward + +__revision__ = "$Id: textwrap.py,v 1.1 2007/09/13 05:14:58 jclain Exp $" + +import string, re + +# Do the right thing with boolean values for all known Python versions. +try: + True, False +except NameError: + (True, False) = (1, 0) + +class TextWrapper: + """ + Object for wrapping/filling text. The public interface consists of + the wrap() and fill() methods; the other methods are just there for + subclasses to override in order to tweak the default behaviour. + If you want to completely replace the main wrapping algorithm, + you'll probably have to override _wrap_chunks(). + + Several instance attributes control various aspects of wrapping: + width (default: 70) + the maximum width of wrapped lines (unless break_long_words + is false) + initial_indent (default: "") + string that will be prepended to the first line of wrapped + output. Counts towards the line's width. + subsequent_indent (default: "") + string that will be prepended to all lines save the first + of wrapped output; also counts towards each line's width. + expand_tabs (default: true) + Expand tabs in input text to spaces before further processing. + Each tab will become 1 .. 8 spaces, depending on its position in + its line. If false, each tab is treated as a single character. + replace_whitespace (default: true) + Replace all whitespace characters in the input text by spaces + after tab expansion. Note that if expand_tabs is false and + replace_whitespace is true, every tab will be converted to a + single space! + fix_sentence_endings (default: false) + Ensure that sentence-ending punctuation is always followed + by two spaces. Off by default becaus the algorithm is + (unavoidably) imperfect. + break_long_words (default: true) + Break words longer than 'width'. If false, those words will not + be broken, and some lines might be longer than 'width'. + """ + + whitespace_trans = string.maketrans(string.whitespace, + ' ' * len(string.whitespace)) + + # This funky little regex is just the trick for splitting + # text up into word-wrappable chunks. E.g. + # "Hello there -- you goof-ball, use the -b option!" + # splits into + # Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option! + # (after stripping out empty strings). + wordsep_re = re.compile(r'(\s+|' # any whitespace + r'-*\w{2,}-(?=\w{2,})|' # hyphenated words + r'(?<=\S)-{2,}(?=\w))') # em-dash + + # XXX will there be a locale-or-charset-aware version of + # string.lowercase in 2.3? + sentence_end_re = re.compile(r'[%s]' # lowercase letter + r'[\.\!\?]' # sentence-ending punct. + r'[\"\']?' # optional end-of-quote + % string.lowercase) + + + def __init__ (self, + width=70, + initial_indent="", + subsequent_indent="", + expand_tabs=True, + replace_whitespace=True, + fix_sentence_endings=False, + break_long_words=True): + self.width = width + self.initial_indent = initial_indent + self.subsequent_indent = subsequent_indent + self.expand_tabs = expand_tabs + self.replace_whitespace = replace_whitespace + self.fix_sentence_endings = fix_sentence_endings + self.break_long_words = break_long_words + + + # -- Private methods ----------------------------------------------- + # (possibly useful for subclasses to override) + + def _munge_whitespace(self, text): + """_munge_whitespace(text : string) -> string + + Munge whitespace in text: expand tabs and convert all other + whitespace characters to spaces. Eg. " foo\tbar\n\nbaz" + becomes " foo bar baz". + """ + if self.expand_tabs: + text = text.expandtabs() + if self.replace_whitespace: + text = text.translate(self.whitespace_trans) + return text + + + def _split(self, text): + """_split(text : string) -> [string] + + Split the text to wrap into indivisible chunks. Chunks are + not quite the same as words; see wrap_chunks() for full + details. As an example, the text + Look, goof-ball -- use the -b option! + breaks into the following chunks: + 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', + 'use', ' ', 'the', ' ', '-b', ' ', 'option!' + """ + chunks = self.wordsep_re.split(text) + chunks = filter(None, chunks) + return chunks + + def _fix_sentence_endings(self, chunks): + """_fix_sentence_endings(chunks : [string]) + + Correct for sentence endings buried in 'chunks'. Eg. when the + original text contains "... foo.\nBar ...", munge_whitespace() + and split() will convert that to [..., "foo.", " ", "Bar", ...] + which has one too few spaces; this method simply changes the one + space to two. + """ + i = 0 + pat = self.sentence_end_re + while i < len(chunks)-1: + if chunks[i+1] == " " and pat.search(chunks[i]): + chunks[i+1] = " " + i += 2 + else: + i += 1 + + def _handle_long_word(self, chunks, cur_line, cur_len, width): + """_handle_long_word(chunks : [string], + cur_line : [string], + cur_len : int, width : int) + + Handle a chunk of text (most likely a word, not whitespace) that + is too long to fit in any line. + """ + space_left = width - cur_len + + # If we're allowed to break long words, then do so: put as much + # of the next chunk onto the current line as will fit. + if self.break_long_words: + cur_line.append(chunks[0][0:space_left]) + chunks[0] = chunks[0][space_left:] + + # Otherwise, we have to preserve the long word intact. Only add + # it to the current line if there's nothing already there -- + # that minimizes how much we violate the width constraint. + elif not cur_line: + cur_line.append(chunks.pop(0)) + + # If we're not allowed to break long words, and there's already + # text on the current line, do nothing. Next time through the + # main loop of _wrap_chunks(), we'll wind up here again, but + # cur_len will be zero, so the next line will be entirely + # devoted to the long word that we can't handle right now. + + def _wrap_chunks(self, chunks): + """_wrap_chunks(chunks : [string]) -> [string] + + Wrap a sequence of text chunks and return a list of lines of + length 'self.width' or less. (If 'break_long_words' is false, + some lines may be longer than this.) Chunks correspond roughly + to words and the whitespace between them: each chunk is + indivisible (modulo 'break_long_words'), but a line break can + come between any two chunks. Chunks should not have internal + whitespace; ie. a chunk is either all whitespace or a "word". + Whitespace chunks will be removed from the beginning and end of + lines, but apart from that whitespace is preserved. + """ + lines = [] + + while chunks: + + # Start the list of chunks that will make up the current line. + # cur_len is just the length of all the chunks in cur_line. + cur_line = [] + cur_len = 0 + + # Figure out which static string will prefix this line. + if lines: + indent = self.subsequent_indent + else: + indent = self.initial_indent + + # Maximum width for this line. + width = self.width - len(indent) + + # First chunk on line is whitespace -- drop it. + if chunks[0].strip() == '': + del chunks[0] + + while chunks: + l = len(chunks[0]) + + # Can at least squeeze this chunk onto the current line. + if cur_len + l <= width: + cur_line.append(chunks.pop(0)) + cur_len += l + + # Nope, this line is full. + else: + break + + # The current line is full, and the next chunk is too big to + # fit on *any* line (not just this one). + if chunks and len(chunks[0]) > width: + self._handle_long_word(chunks, cur_line, cur_len, width) + + # If the last chunk on this line is all whitespace, drop it. + if cur_line and cur_line[-1].strip() == '': + del cur_line[-1] + + # Convert current line back to a string and store it in list + # of all lines (return value). + if cur_line: + lines.append(indent + ''.join(cur_line)) + + return lines + + + # -- Public interface ---------------------------------------------- + + def wrap(self, text): + """wrap(text : string) -> [string] + + Reformat the single paragraph in 'text' so it fits in lines of + no more than 'self.width' columns, and return a list of wrapped + lines. Tabs in 'text' are expanded with string.expandtabs(), + and all other whitespace characters (including newline) are + converted to space. + """ + text = self._munge_whitespace(text) + indent = self.initial_indent + if len(text) + len(indent) <= self.width: + return [indent + text] + chunks = self._split(text) + if self.fix_sentence_endings: + self._fix_sentence_endings(chunks) + return self._wrap_chunks(chunks) + + def fill(self, text): + """fill(text : string) -> string + + Reformat the single paragraph in 'text' to fit in lines of no + more than 'self.width' columns, and return a new string + containing the entire wrapped paragraph. + """ + return "\n".join(self.wrap(text)) + + +# -- Convenience interface --------------------------------------------- + +def wrap(text, width=70, **kwargs): + """Wrap a single paragraph of text, returning a list of wrapped lines. + + Reformat the single paragraph in 'text' so it fits in lines of no + more than 'width' columns, and return a list of wrapped lines. By + default, tabs in 'text' are expanded with string.expandtabs(), and + all other whitespace characters (including newline) are converted to + space. See TextWrapper class for available keyword args to customize + wrapping behaviour. + """ + w = TextWrapper(width=width, **kwargs) + return w.wrap(text) + +def fill(text, width=70, **kwargs): + """Fill a single paragraph of text, returning a new string. + + Reformat the single paragraph in 'text' to fit in lines of no more + than 'width' columns, and return a new string containing the entire + wrapped paragraph. As with wrap(), tabs are expanded and other + whitespace characters converted to space. See TextWrapper class for + available keyword args to customize wrapping behaviour. + """ + w = TextWrapper(width=width, **kwargs) + return w.fill(text) diff --git a/pyulib/src/ulib/ext/simplejson/__init__.py b/pyulib/src/ulib/ext/simplejson/__init__.py new file mode 100644 index 0000000..d5b4d39 --- /dev/null +++ b/pyulib/src/ulib/ext/simplejson/__init__.py @@ -0,0 +1,318 @@ +r"""JSON (JavaScript Object Notation) is a subset of +JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data +interchange format. + +:mod:`simplejson` exposes an API familiar to users of the standard library +:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained +version of the :mod:`json` library contained in Python 2.6, but maintains +compatibility with Python 2.4 and Python 2.5 and (currently) has +significant performance advantages, even without using the optional C +extension for speedups. + +Encoding basic Python object hierarchies:: + + >>> import simplejson as json + >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) + '["foo", {"bar": ["baz", null, 1.0, 2]}]' + >>> print json.dumps("\"foo\bar") + "\"foo\bar" + >>> print json.dumps(u'\u1234') + "\u1234" + >>> print json.dumps('\\') + "\\" + >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) + {"a": 0, "b": 0, "c": 0} + >>> from StringIO import StringIO + >>> io = StringIO() + >>> json.dump(['streaming API'], io) + >>> io.getvalue() + '["streaming API"]' + +Compact encoding:: + + >>> import simplejson as json + >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) + '[1,2,3,{"4":5,"6":7}]' + +Pretty printing:: + + >>> import simplejson as json + >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) + >>> print '\n'.join([l.rstrip() for l in s.splitlines()]) + { + "4": 5, + "6": 7 + } + +Decoding JSON:: + + >>> import simplejson as json + >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] + >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj + True + >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar' + True + >>> from StringIO import StringIO + >>> io = StringIO('["streaming API"]') + >>> json.load(io)[0] == 'streaming API' + True + +Specializing JSON object decoding:: + + >>> import simplejson as json + >>> def as_complex(dct): + ... if '__complex__' in dct: + ... return complex(dct['real'], dct['imag']) + ... return dct + ... + >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}', + ... object_hook=as_complex) + (1+2j) + >>> import decimal + >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1') + True + +Specializing JSON object encoding:: + + >>> import simplejson as json + >>> def encode_complex(obj): + ... if isinstance(obj, complex): + ... return [obj.real, obj.imag] + ... raise TypeError(repr(o) + " is not JSON serializable") + ... + >>> json.dumps(2 + 1j, default=encode_complex) + '[2.0, 1.0]' + >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j) + '[2.0, 1.0]' + >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j)) + '[2.0, 1.0]' + + +Using simplejson.tool from the shell to validate and pretty-print:: + + $ echo '{"json":"obj"}' | python -m simplejson.tool + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -m simplejson.tool + Expecting property name: line 1 column 2 (char 2) +""" +__version__ = '2.0.9' +__all__ = [ + 'dump', 'dumps', 'load', 'loads', + 'JSONDecoder', 'JSONEncoder', +] + +__author__ = 'Bob Ippolito ' + +from decoder import JSONDecoder +from encoder import JSONEncoder + +_default_encoder = JSONEncoder( + skipkeys=False, + ensure_ascii=True, + check_circular=True, + allow_nan=True, + indent=None, + separators=None, + encoding='utf-8', + default=None, +) + +def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, **kw): + """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is true then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the some chunks written to ``fp`` + may be ``unicode`` instances, subject to normal Python ``str`` to + ``unicode`` coercion rules. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter()``) this is likely + to cause an error. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and object + members will be pretty-printed with that indent level. An indent level + of 0 will only insert newlines. ``None`` is the most compact representation. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (not skipkeys and ensure_ascii and + check_circular and allow_nan and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not kw): + iterable = _default_encoder.iterencode(obj) + else: + if cls is None: + cls = JSONEncoder + iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, + default=default, **kw).iterencode(obj) + # could accelerate with writelines in some versions of Python, at + # a debuggability cost + for chunk in iterable: + fp.write(chunk) + + +def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, **kw): + """Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is false then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is false, then the return value will be a + ``unicode`` instance subject to normal Python ``str`` to ``unicode`` + coercion rules instead of being escaped to an ASCII ``str``. + + If ``check_circular`` is false, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is false, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and + object members will be pretty-printed with that indent level. An indent + level of 0 will only insert newlines. ``None`` is the most compact + representation. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (not skipkeys and ensure_ascii and + check_circular and allow_nan and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not kw): + return _default_encoder.encode(obj) + if cls is None: + cls = JSONEncoder + return cls( + skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, default=default, + **kw).encode(obj) + + +_default_decoder = JSONDecoder(encoding=None, object_hook=None) + + +def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, **kw): + """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing + a JSON document) to a Python object. + + If the contents of ``fp`` is encoded with an ASCII based encoding other + than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must + be specified. Encodings that are not ASCII based (such as UCS-2) are + not allowed, and should be wrapped with + ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode`` + object and passed to ``loads()`` + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + return loads(fp.read(), + encoding=encoding, cls=cls, object_hook=object_hook, + parse_float=parse_float, parse_int=parse_int, + parse_constant=parse_constant, **kw) + + +def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, **kw): + """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON + document) to a Python object. + + If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding + other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name + must be specified. Encodings that are not ASCII based (such as UCS-2) + are not allowed and should be decoded to ``unicode`` first. + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN, null, true, false. + This can be used to raise an exception if invalid JSON numbers + are encountered. + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + if (cls is None and encoding is None and object_hook is None and + parse_int is None and parse_float is None and + parse_constant is None and not kw): + return _default_decoder.decode(s) + if cls is None: + cls = JSONDecoder + if object_hook is not None: + kw['object_hook'] = object_hook + if parse_float is not None: + kw['parse_float'] = parse_float + if parse_int is not None: + kw['parse_int'] = parse_int + if parse_constant is not None: + kw['parse_constant'] = parse_constant + return cls(encoding=encoding, **kw).decode(s) diff --git a/pyulib/src/ulib/ext/simplejson/__init__.pyc b/pyulib/src/ulib/ext/simplejson/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f590aeef5d7a1104dbc7c56cf52fde5e4b97da7 GIT binary patch literal 13426 zcmeHOUvt|=k{?ReAJcyl+sSRRxviUJrHbH%q9Vn~CQ9Vntm9O9w^nhT?7b?swu+P`SXeh zuZr**U4J81_jm~U+utTVr_NXT^9}Lsd9eqAtF8lj&h>ed`(Qfodxb~+1ATrY7WOc^ z>s(!gKNH_#LCjM1&|HLXantJ}d|T{#KPTzDeNl+t?tZl+z2Ewe{M{grvO<0}e5is# z?xclZL}}8IpN)$m+ge|LsMDmE<_9-qq@^!)In=6<>8M#7tdEnze=NOQH-G>6+U7@_ z@`F5-DvS)JQmatJ7MPZ|1!-;9z%<-yob4wM|F`NS`J&F0o54Yvk5nq;w} z*#}ZR&Qh&FA(5Zoxh+TjB#I+HmqjW;Jl9}H7GouK;U}Swx#MV<`}wgYHSu#j_TvG@ zhtg2bqTn!A1Bt21Sm~a;UC{c7h1L2lGRSy{B^1Zjh<=tE7mJJ96yn zrj8DhXcPs0Qpil@EVm?3(hncuk$<3cSE@%UVcIF?E_IX~@Y>RhRN}|-W|Nkg@RC44 z>P)Gy%rwM*D+$t&1`K^21&+7W*vB|h5Nv=iy|vYBO8mQV1Ew*_(!7vUosqstI#mxg zqp+N0+S_lB(zM-`PufF2Z*NHt-(TZ?CrwmWZuH*o%FWKngAR{v@3+vqh2AanZf(hZ ze1DDmq>N+N`@zYBb~RI$LuovD&&=1lPbBWsa6_~9;Q6D<_Wg2W^MmVcH-_(8&mYjf zf4}N`|9kp9X$4pY7TFrwZ=b$Tx>7@i{X=!Ex9{eqGV?xZaciwNk3OmR80G0i?iN_| z!R@b{BHVq|D@x^d)yp%aNpY20pcP%@3Ofs$$0ln(=*lRac8t>Afhr#PajEQZXl7
S`y+h+>I7VAmHD|}q`BVq+Fkr?wL6B- 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/simplejson/_speedups.c b/pyulib/src/ulib/ext/simplejson/_speedups.c new file mode 100644 index 0000000..23b5f4a --- /dev/null +++ b/pyulib/src/ulib/ext/simplejson/_speedups.c @@ -0,0 +1,2329 @@ +#include "Python.h" +#include "structmember.h" +#if PY_VERSION_HEX < 0x02060000 && !defined(Py_TYPE) +#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +#endif +#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) +typedef int Py_ssize_t; +#define PY_SSIZE_T_MAX INT_MAX +#define PY_SSIZE_T_MIN INT_MIN +#define PyInt_FromSsize_t PyInt_FromLong +#define PyInt_AsSsize_t PyInt_AsLong +#endif +#ifndef Py_IS_FINITE +#define Py_IS_FINITE(X) (!Py_IS_INFINITY(X) && !Py_IS_NAN(X)) +#endif + +#ifdef __GNUC__ +#define UNUSED __attribute__((__unused__)) +#else +#define UNUSED +#endif + +#define DEFAULT_ENCODING "utf-8" + +#define PyScanner_Check(op) PyObject_TypeCheck(op, &PyScannerType) +#define PyScanner_CheckExact(op) (Py_TYPE(op) == &PyScannerType) +#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType) +#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType) + +static PyTypeObject PyScannerType; +static PyTypeObject PyEncoderType; + +typedef struct _PyScannerObject { + PyObject_HEAD + PyObject *encoding; + PyObject *strict; + PyObject *object_hook; + PyObject *parse_float; + PyObject *parse_int; + PyObject *parse_constant; +} PyScannerObject; + +static PyMemberDef scanner_members[] = { + {"encoding", T_OBJECT, offsetof(PyScannerObject, encoding), READONLY, "encoding"}, + {"strict", T_OBJECT, offsetof(PyScannerObject, strict), READONLY, "strict"}, + {"object_hook", T_OBJECT, offsetof(PyScannerObject, object_hook), READONLY, "object_hook"}, + {"parse_float", T_OBJECT, offsetof(PyScannerObject, parse_float), READONLY, "parse_float"}, + {"parse_int", T_OBJECT, offsetof(PyScannerObject, parse_int), READONLY, "parse_int"}, + {"parse_constant", T_OBJECT, offsetof(PyScannerObject, parse_constant), READONLY, "parse_constant"}, + {NULL} +}; + +typedef struct _PyEncoderObject { + PyObject_HEAD + PyObject *markers; + PyObject *defaultfn; + PyObject *encoder; + PyObject *indent; + PyObject *key_separator; + PyObject *item_separator; + PyObject *sort_keys; + PyObject *skipkeys; + int fast_encode; + int allow_nan; +} PyEncoderObject; + +static PyMemberDef encoder_members[] = { + {"markers", T_OBJECT, offsetof(PyEncoderObject, markers), READONLY, "markers"}, + {"default", T_OBJECT, offsetof(PyEncoderObject, defaultfn), READONLY, "default"}, + {"encoder", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoder"}, + {"indent", T_OBJECT, offsetof(PyEncoderObject, indent), READONLY, "indent"}, + {"key_separator", T_OBJECT, offsetof(PyEncoderObject, key_separator), READONLY, "key_separator"}, + {"item_separator", T_OBJECT, offsetof(PyEncoderObject, item_separator), READONLY, "item_separator"}, + {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"}, + {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys), READONLY, "skipkeys"}, + {NULL} +}; + +static Py_ssize_t +ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars); +static PyObject * +ascii_escape_unicode(PyObject *pystr); +static PyObject * +ascii_escape_str(PyObject *pystr); +static PyObject * +py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr); +void init_speedups(void); +static PyObject * +scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr); +static PyObject * +scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr); +static PyObject * +_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx); +static PyObject * +scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds); +static int +scanner_init(PyObject *self, PyObject *args, PyObject *kwds); +static void +scanner_dealloc(PyObject *self); +static int +scanner_clear(PyObject *self); +static PyObject * +encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds); +static int +encoder_init(PyObject *self, PyObject *args, PyObject *kwds); +static void +encoder_dealloc(PyObject *self); +static int +encoder_clear(PyObject *self); +static int +encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level); +static int +encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level); +static int +encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level); +static PyObject * +_encoded_const(PyObject *const); +static void +raise_errmsg(char *msg, PyObject *s, Py_ssize_t end); +static PyObject * +encoder_encode_string(PyEncoderObject *s, PyObject *obj); +static int +_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr); +static PyObject * +_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr); +static PyObject * +encoder_encode_float(PyEncoderObject *s, PyObject *obj); + +#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"') +#define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r')) + +#define MIN_EXPANSION 6 +#ifdef Py_UNICODE_WIDE +#define MAX_EXPANSION (2 * MIN_EXPANSION) +#else +#define MAX_EXPANSION MIN_EXPANSION +#endif + +static int +_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr) +{ + /* PyObject to Py_ssize_t converter */ + *size_ptr = PyInt_AsSsize_t(o); + if (*size_ptr == -1 && PyErr_Occurred()); + return 1; + return 0; +} + +static PyObject * +_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr) +{ + /* Py_ssize_t to PyObject converter */ + return PyInt_FromSsize_t(*size_ptr); +} + +static Py_ssize_t +ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars) +{ + /* Escape unicode code point c to ASCII escape sequences + in char *output. output must have at least 12 bytes unused to + accommodate an escaped surrogate pair "\uXXXX\uXXXX" */ + output[chars++] = '\\'; + switch (c) { + case '\\': output[chars++] = (char)c; break; + case '"': output[chars++] = (char)c; break; + case '\b': output[chars++] = 'b'; break; + case '\f': output[chars++] = 'f'; break; + case '\n': output[chars++] = 'n'; break; + case '\r': output[chars++] = 'r'; break; + case '\t': output[chars++] = 't'; break; + default: +#ifdef Py_UNICODE_WIDE + if (c >= 0x10000) { + /* UTF-16 surrogate pair */ + Py_UNICODE v = c - 0x10000; + c = 0xd800 | ((v >> 10) & 0x3ff); + output[chars++] = 'u'; + output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 8) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 4) & 0xf]; + output[chars++] = "0123456789abcdef"[(c ) & 0xf]; + c = 0xdc00 | (v & 0x3ff); + output[chars++] = '\\'; + } +#endif + output[chars++] = 'u'; + output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 8) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 4) & 0xf]; + output[chars++] = "0123456789abcdef"[(c ) & 0xf]; + } + return chars; +} + +static PyObject * +ascii_escape_unicode(PyObject *pystr) +{ + /* Take a PyUnicode pystr and return a new ASCII-only escaped PyString */ + Py_ssize_t i; + Py_ssize_t input_chars; + Py_ssize_t output_size; + Py_ssize_t max_output_size; + Py_ssize_t chars; + PyObject *rval; + char *output; + Py_UNICODE *input_unicode; + + input_chars = PyUnicode_GET_SIZE(pystr); + input_unicode = PyUnicode_AS_UNICODE(pystr); + + /* One char input can be up to 6 chars output, estimate 4 of these */ + output_size = 2 + (MIN_EXPANSION * 4) + input_chars; + max_output_size = 2 + (input_chars * MAX_EXPANSION); + rval = PyString_FromStringAndSize(NULL, output_size); + if (rval == NULL) { + return NULL; + } + output = PyString_AS_STRING(rval); + chars = 0; + output[chars++] = '"'; + for (i = 0; i < input_chars; i++) { + Py_UNICODE c = input_unicode[i]; + if (S_CHAR(c)) { + output[chars++] = (char)c; + } + else { + chars = ascii_escape_char(c, output, chars); + } + if (output_size - chars < (1 + MAX_EXPANSION)) { + /* There's more than four, so let's resize by a lot */ + Py_ssize_t new_output_size = output_size * 2; + /* This is an upper bound */ + if (new_output_size > max_output_size) { + new_output_size = max_output_size; + } + /* Make sure that the output size changed before resizing */ + if (new_output_size != output_size) { + output_size = new_output_size; + if (_PyString_Resize(&rval, output_size) == -1) { + return NULL; + } + output = PyString_AS_STRING(rval); + } + } + } + output[chars++] = '"'; + if (_PyString_Resize(&rval, chars) == -1) { + return NULL; + } + return rval; +} + +static PyObject * +ascii_escape_str(PyObject *pystr) +{ + /* Take a PyString pystr and return a new ASCII-only escaped PyString */ + Py_ssize_t i; + Py_ssize_t input_chars; + Py_ssize_t output_size; + Py_ssize_t chars; + PyObject *rval; + char *output; + char *input_str; + + input_chars = PyString_GET_SIZE(pystr); + input_str = PyString_AS_STRING(pystr); + + /* Fast path for a string that's already ASCII */ + for (i = 0; i < input_chars; i++) { + Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i]; + if (!S_CHAR(c)) { + /* If we have to escape something, scan the string for unicode */ + Py_ssize_t j; + for (j = i; j < input_chars; j++) { + c = (Py_UNICODE)(unsigned char)input_str[j]; + if (c > 0x7f) { + /* We hit a non-ASCII character, bail to unicode mode */ + PyObject *uni; + uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict"); + if (uni == NULL) { + return NULL; + } + rval = ascii_escape_unicode(uni); + Py_DECREF(uni); + return rval; + } + } + break; + } + } + + if (i == input_chars) { + /* Input is already ASCII */ + output_size = 2 + input_chars; + } + else { + /* One char input can be up to 6 chars output, estimate 4 of these */ + output_size = 2 + (MIN_EXPANSION * 4) + input_chars; + } + rval = PyString_FromStringAndSize(NULL, output_size); + if (rval == NULL) { + return NULL; + } + output = PyString_AS_STRING(rval); + output[0] = '"'; + + /* We know that everything up to i is ASCII already */ + chars = i + 1; + memcpy(&output[1], input_str, i); + + for (; i < input_chars; i++) { + Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i]; + if (S_CHAR(c)) { + output[chars++] = (char)c; + } + else { + chars = ascii_escape_char(c, output, chars); + } + /* An ASCII char can't possibly expand to a surrogate! */ + if (output_size - chars < (1 + MIN_EXPANSION)) { + /* There's more than four, so let's resize by a lot */ + output_size *= 2; + if (output_size > 2 + (input_chars * MIN_EXPANSION)) { + output_size = 2 + (input_chars * MIN_EXPANSION); + } + if (_PyString_Resize(&rval, output_size) == -1) { + return NULL; + } + output = PyString_AS_STRING(rval); + } + } + output[chars++] = '"'; + if (_PyString_Resize(&rval, chars) == -1) { + return NULL; + } + return rval; +} + +static void +raise_errmsg(char *msg, PyObject *s, Py_ssize_t end) +{ + /* Use the Python function simplejson.decoder.errmsg to raise a nice + looking ValueError exception */ + static PyObject *errmsg_fn = NULL; + PyObject *pymsg; + if (errmsg_fn == NULL) { + PyObject *decoder = PyImport_ImportModule("simplejson.decoder"); + if (decoder == NULL) + return; + errmsg_fn = PyObject_GetAttrString(decoder, "errmsg"); + Py_DECREF(decoder); + if (errmsg_fn == NULL) + return; + } + pymsg = PyObject_CallFunction(errmsg_fn, "(zOO&)", msg, s, _convertPyInt_FromSsize_t, &end); + if (pymsg) { + PyErr_SetObject(PyExc_ValueError, pymsg); + Py_DECREF(pymsg); + } +} + +static PyObject * +join_list_unicode(PyObject *lst) +{ + /* return u''.join(lst) */ + static PyObject *joinfn = NULL; + if (joinfn == NULL) { + PyObject *ustr = PyUnicode_FromUnicode(NULL, 0); + if (ustr == NULL) + return NULL; + + joinfn = PyObject_GetAttrString(ustr, "join"); + Py_DECREF(ustr); + if (joinfn == NULL) + return NULL; + } + return PyObject_CallFunctionObjArgs(joinfn, lst, NULL); +} + +static PyObject * +join_list_string(PyObject *lst) +{ + /* return ''.join(lst) */ + static PyObject *joinfn = NULL; + if (joinfn == NULL) { + PyObject *ustr = PyString_FromStringAndSize(NULL, 0); + if (ustr == NULL) + return NULL; + + joinfn = PyObject_GetAttrString(ustr, "join"); + Py_DECREF(ustr); + if (joinfn == NULL) + return NULL; + } + return PyObject_CallFunctionObjArgs(joinfn, lst, NULL); +} + +static PyObject * +_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx) { + /* return (rval, idx) tuple, stealing reference to rval */ + PyObject *tpl; + PyObject *pyidx; + /* + steal a reference to rval, returns (rval, idx) + */ + if (rval == NULL) { + return NULL; + } + pyidx = PyInt_FromSsize_t(idx); + if (pyidx == NULL) { + Py_DECREF(rval); + return NULL; + } + tpl = PyTuple_New(2); + if (tpl == NULL) { + Py_DECREF(pyidx); + Py_DECREF(rval); + return NULL; + } + PyTuple_SET_ITEM(tpl, 0, rval); + PyTuple_SET_ITEM(tpl, 1, pyidx); + return tpl; +} + +static PyObject * +scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr) +{ + /* Read the JSON string from PyString pystr. + end is the index of the first character after the quote. + encoding is the encoding of pystr (must be an ASCII superset) + if strict is zero then literal control characters are allowed + *next_end_ptr is a return-by-reference index of the character + after the end quote + + Return value is a new PyString (if ASCII-only) or PyUnicode + */ + PyObject *rval; + Py_ssize_t len = PyString_GET_SIZE(pystr); + Py_ssize_t begin = end - 1; + Py_ssize_t next = begin; + int has_unicode = 0; + char *buf = PyString_AS_STRING(pystr); + PyObject *chunks = PyList_New(0); + if (chunks == NULL) { + goto bail; + } + if (end < 0 || len <= end) { + PyErr_SetString(PyExc_ValueError, "end is out of bounds"); + goto bail; + } + while (1) { + /* Find the end of the string or the next escape */ + Py_UNICODE c = 0; + PyObject *chunk = NULL; + for (next = end; next < len; next++) { + c = (unsigned char)buf[next]; + if (c == '"' || c == '\\') { + break; + } + else if (strict && c <= 0x1f) { + raise_errmsg("Invalid control character at", pystr, next); + goto bail; + } + else if (c > 0x7f) { + has_unicode = 1; + } + } + if (!(c == '"' || c == '\\')) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + /* Pick up this chunk if it's not zero length */ + if (next != end) { + PyObject *strchunk = PyString_FromStringAndSize(&buf[end], next - end); + if (strchunk == NULL) { + goto bail; + } + if (has_unicode) { + chunk = PyUnicode_FromEncodedObject(strchunk, encoding, NULL); + Py_DECREF(strchunk); + if (chunk == NULL) { + goto bail; + } + } + else { + chunk = strchunk; + } + if (PyList_Append(chunks, chunk)) { + Py_DECREF(chunk); + goto bail; + } + Py_DECREF(chunk); + } + next++; + if (c == '"') { + end = next; + break; + } + if (next == len) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + c = buf[next]; + if (c != 'u') { + /* Non-unicode backslash escapes */ + end = next + 1; + switch (c) { + case '"': break; + case '\\': break; + case '/': break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + default: c = 0; + } + if (c == 0) { + raise_errmsg("Invalid \\escape", pystr, end - 2); + goto bail; + } + } + else { + c = 0; + next++; + end = next + 4; + if (end >= len) { + raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1); + goto bail; + } + /* Decode 4 hex digits */ + for (; next < end; next++) { + Py_UNICODE digit = buf[next]; + c <<= 4; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } +#ifdef Py_UNICODE_WIDE + /* Surrogate pair */ + if ((c & 0xfc00) == 0xd800) { + Py_UNICODE c2 = 0; + if (end + 6 >= len) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + if (buf[next++] != '\\' || buf[next++] != 'u') { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + end += 6; + /* Decode 4 hex digits */ + for (; next < end; next++) { + c2 <<= 4; + Py_UNICODE digit = buf[next]; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c2 |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c2 |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c2 |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } + if ((c2 & 0xfc00) != 0xdc00) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00)); + } + else if ((c & 0xfc00) == 0xdc00) { + raise_errmsg("Unpaired low surrogate", pystr, end - 5); + goto bail; + } +#endif + } + if (c > 0x7f) { + has_unicode = 1; + } + if (has_unicode) { + chunk = PyUnicode_FromUnicode(&c, 1); + if (chunk == NULL) { + goto bail; + } + } + else { + char c_char = Py_CHARMASK(c); + chunk = PyString_FromStringAndSize(&c_char, 1); + if (chunk == NULL) { + goto bail; + } + } + if (PyList_Append(chunks, chunk)) { + Py_DECREF(chunk); + goto bail; + } + Py_DECREF(chunk); + } + + rval = join_list_string(chunks); + if (rval == NULL) { + goto bail; + } + Py_CLEAR(chunks); + *next_end_ptr = end; + return rval; +bail: + *next_end_ptr = -1; + Py_XDECREF(chunks); + return NULL; +} + + +static PyObject * +scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr) +{ + /* Read the JSON string from PyUnicode pystr. + end is the index of the first character after the quote. + if strict is zero then literal control characters are allowed + *next_end_ptr is a return-by-reference index of the character + after the end quote + + Return value is a new PyUnicode + */ + PyObject *rval; + Py_ssize_t len = PyUnicode_GET_SIZE(pystr); + Py_ssize_t begin = end - 1; + Py_ssize_t next = begin; + const Py_UNICODE *buf = PyUnicode_AS_UNICODE(pystr); + PyObject *chunks = PyList_New(0); + if (chunks == NULL) { + goto bail; + } + if (end < 0 || len <= end) { + PyErr_SetString(PyExc_ValueError, "end is out of bounds"); + goto bail; + } + while (1) { + /* Find the end of the string or the next escape */ + Py_UNICODE c = 0; + PyObject *chunk = NULL; + for (next = end; next < len; next++) { + c = buf[next]; + if (c == '"' || c == '\\') { + break; + } + else if (strict && c <= 0x1f) { + raise_errmsg("Invalid control character at", pystr, next); + goto bail; + } + } + if (!(c == '"' || c == '\\')) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + /* Pick up this chunk if it's not zero length */ + if (next != end) { + chunk = PyUnicode_FromUnicode(&buf[end], next - end); + if (chunk == NULL) { + goto bail; + } + if (PyList_Append(chunks, chunk)) { + Py_DECREF(chunk); + goto bail; + } + Py_DECREF(chunk); + } + next++; + if (c == '"') { + end = next; + break; + } + if (next == len) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + c = buf[next]; + if (c != 'u') { + /* Non-unicode backslash escapes */ + end = next + 1; + switch (c) { + case '"': break; + case '\\': break; + case '/': break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + default: c = 0; + } + if (c == 0) { + raise_errmsg("Invalid \\escape", pystr, end - 2); + goto bail; + } + } + else { + c = 0; + next++; + end = next + 4; + if (end >= len) { + raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1); + goto bail; + } + /* Decode 4 hex digits */ + for (; next < end; next++) { + Py_UNICODE digit = buf[next]; + c <<= 4; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } +#ifdef Py_UNICODE_WIDE + /* Surrogate pair */ + if ((c & 0xfc00) == 0xd800) { + Py_UNICODE c2 = 0; + if (end + 6 >= len) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + if (buf[next++] != '\\' || buf[next++] != 'u') { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + end += 6; + /* Decode 4 hex digits */ + for (; next < end; next++) { + c2 <<= 4; + Py_UNICODE digit = buf[next]; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c2 |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c2 |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c2 |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } + if ((c2 & 0xfc00) != 0xdc00) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00)); + } + else if ((c & 0xfc00) == 0xdc00) { + raise_errmsg("Unpaired low surrogate", pystr, end - 5); + goto bail; + } +#endif + } + chunk = PyUnicode_FromUnicode(&c, 1); + if (chunk == NULL) { + goto bail; + } + if (PyList_Append(chunks, chunk)) { + Py_DECREF(chunk); + goto bail; + } + Py_DECREF(chunk); + } + + rval = join_list_unicode(chunks); + if (rval == NULL) { + goto bail; + } + Py_DECREF(chunks); + *next_end_ptr = end; + return rval; +bail: + *next_end_ptr = -1; + Py_XDECREF(chunks); + return NULL; +} + +PyDoc_STRVAR(pydoc_scanstring, + "scanstring(basestring, end, encoding, strict=True) -> (str, end)\n" + "\n" + "Scan the string s for a JSON string. End is the index of the\n" + "character in s after the quote that started the JSON string.\n" + "Unescapes all valid JSON string escape sequences and raises ValueError\n" + "on attempt to decode an invalid string. If strict is False then literal\n" + "control characters are allowed in the string.\n" + "\n" + "Returns a tuple of the decoded string and the index of the character in s\n" + "after the end quote." +); + +static PyObject * +py_scanstring(PyObject* self UNUSED, PyObject *args) +{ + PyObject *pystr; + PyObject *rval; + Py_ssize_t end; + Py_ssize_t next_end = -1; + char *encoding = NULL; + int strict = 1; + if (!PyArg_ParseTuple(args, "OO&|zi:scanstring", &pystr, _convertPyInt_AsSsize_t, &end, &encoding, &strict)) { + return NULL; + } + if (encoding == NULL) { + encoding = DEFAULT_ENCODING; + } + if (PyString_Check(pystr)) { + rval = scanstring_str(pystr, end, encoding, strict, &next_end); + } + else if (PyUnicode_Check(pystr)) { + rval = scanstring_unicode(pystr, end, strict, &next_end); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } + return _build_rval_index_tuple(rval, next_end); +} + +PyDoc_STRVAR(pydoc_encode_basestring_ascii, + "encode_basestring_ascii(basestring) -> str\n" + "\n" + "Return an ASCII-only JSON representation of a Python string" +); + +static PyObject * +py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr) +{ + /* Return an ASCII-only JSON representation of a Python string */ + /* METH_O */ + if (PyString_Check(pystr)) { + return ascii_escape_str(pystr); + } + else if (PyUnicode_Check(pystr)) { + return ascii_escape_unicode(pystr); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } +} + +static void +scanner_dealloc(PyObject *self) +{ + /* Deallocate scanner object */ + scanner_clear(self); + Py_TYPE(self)->tp_free(self); +} + +static int +scanner_traverse(PyObject *self, visitproc visit, void *arg) +{ + PyScannerObject *s; + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + Py_VISIT(s->encoding); + Py_VISIT(s->strict); + Py_VISIT(s->object_hook); + Py_VISIT(s->parse_float); + Py_VISIT(s->parse_int); + Py_VISIT(s->parse_constant); + return 0; +} + +static int +scanner_clear(PyObject *self) +{ + PyScannerObject *s; + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + Py_CLEAR(s->encoding); + Py_CLEAR(s->strict); + Py_CLEAR(s->object_hook); + Py_CLEAR(s->parse_float); + Py_CLEAR(s->parse_int); + Py_CLEAR(s->parse_constant); + return 0; +} + +static PyObject * +_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON object from PyString pystr. + idx is the index of the first character after the opening curly brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing curly brace. + + Returns a new PyObject (usually a dict, but object_hook can change that) + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + PyObject *rval = PyDict_New(); + PyObject *key = NULL; + PyObject *val = NULL; + char *encoding = PyString_AS_STRING(s->encoding); + int strict = PyObject_IsTrue(s->strict); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after { */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the object is non-empty */ + if (idx <= end_idx && str[idx] != '}') { + while (idx <= end_idx) { + /* read key */ + if (str[idx] != '"') { + raise_errmsg("Expecting property name", pystr, idx); + goto bail; + } + key = scanstring_str(pystr, idx + 1, encoding, strict, &next_idx); + if (key == NULL) + goto bail; + idx = next_idx; + + /* skip whitespace between key and : delimiter, read :, skip whitespace */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + if (idx > end_idx || str[idx] != ':') { + raise_errmsg("Expecting : delimiter", pystr, idx); + goto bail; + } + idx++; + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* read any JSON data type */ + val = scan_once_str(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (PyDict_SetItem(rval, key, val) == -1) + goto bail; + + Py_CLEAR(key); + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace before } or , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the object is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == '}') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting , delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , delimiter */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + /* verify that idx < end_idx, str[idx] should be '}' */ + if (idx > end_idx || str[idx] != '}') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + /* if object_hook is not None: rval = object_hook(rval) */ + if (s->object_hook != Py_None) { + val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL); + if (val == NULL) + goto bail; + Py_DECREF(rval); + rval = val; + val = NULL; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(key); + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON object from PyUnicode pystr. + idx is the index of the first character after the opening curly brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing curly brace. + + Returns a new PyObject (usually a dict, but object_hook can change that) + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + PyObject *val = NULL; + PyObject *rval = PyDict_New(); + PyObject *key = NULL; + int strict = PyObject_IsTrue(s->strict); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after { */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the object is non-empty */ + if (idx <= end_idx && str[idx] != '}') { + while (idx <= end_idx) { + /* read key */ + if (str[idx] != '"') { + raise_errmsg("Expecting property name", pystr, idx); + goto bail; + } + key = scanstring_unicode(pystr, idx + 1, strict, &next_idx); + if (key == NULL) + goto bail; + idx = next_idx; + + /* skip whitespace between key and : delimiter, read :, skip whitespace */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + if (idx > end_idx || str[idx] != ':') { + raise_errmsg("Expecting : delimiter", pystr, idx); + goto bail; + } + idx++; + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* read any JSON term */ + val = scan_once_unicode(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (PyDict_SetItem(rval, key, val) == -1) + goto bail; + + Py_CLEAR(key); + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace before } or , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the object is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == '}') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting , delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , delimiter */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be '}' */ + if (idx > end_idx || str[idx] != '}') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + + /* if object_hook is not None: rval = object_hook(rval) */ + if (s->object_hook != Py_None) { + val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL); + if (val == NULL) + goto bail; + Py_DECREF(rval); + rval = val; + val = NULL; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(key); + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_array_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON array from PyString pystr. + idx is the index of the first character after the opening brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing brace. + + Returns a new PyList + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + PyObject *val = NULL; + PyObject *rval = PyList_New(0); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after [ */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the array is non-empty */ + if (idx <= end_idx && str[idx] != ']') { + while (idx <= end_idx) { + + /* read any JSON term and de-tuplefy the (rval, idx) */ + val = scan_once_str(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (PyList_Append(rval, val) == -1) + goto bail; + + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace between term and , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the array is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == ']') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting , delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be ']' */ + if (idx > end_idx || str[idx] != ']') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON array from PyString pystr. + idx is the index of the first character after the opening brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing brace. + + Returns a new PyList + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + PyObject *val = NULL; + PyObject *rval = PyList_New(0); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after [ */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the array is non-empty */ + if (idx <= end_idx && str[idx] != ']') { + while (idx <= end_idx) { + + /* read any JSON term */ + val = scan_once_unicode(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (PyList_Append(rval, val) == -1) + goto bail; + + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace between term and , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the array is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == ']') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting , delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be ']' */ + if (idx > end_idx || str[idx] != ']') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_constant(PyScannerObject *s, char *constant, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON constant from PyString pystr. + constant is the constant string that was found + ("NaN", "Infinity", "-Infinity"). + idx is the index of the first character of the constant + *next_idx_ptr is a return-by-reference index to the first character after + the constant. + + Returns the result of parse_constant + */ + PyObject *cstr; + PyObject *rval; + /* constant is "NaN", "Infinity", or "-Infinity" */ + cstr = PyString_InternFromString(constant); + if (cstr == NULL) + return NULL; + + /* rval = parse_constant(constant) */ + rval = PyObject_CallFunctionObjArgs(s->parse_constant, cstr, NULL); + idx += PyString_GET_SIZE(cstr); + Py_DECREF(cstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +_match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) { + /* Read a JSON number from PyString pystr. + idx is the index of the first character of the number + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of that number: + PyInt, PyLong, or PyFloat. + May return other types if parse_int or parse_float are set + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + Py_ssize_t idx = start; + int is_float = 0; + PyObject *rval; + PyObject *numstr; + + /* read a sign if it's there, make sure it's not the end of the string */ + if (str[idx] == '-') { + idx++; + if (idx > end_idx) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + } + + /* read as many integer digits as we find as long as it doesn't start with 0 */ + if (str[idx] >= '1' && str[idx] <= '9') { + idx++; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + /* if it starts with 0 we only expect one integer digit */ + else if (str[idx] == '0') { + idx++; + } + /* no integer digits, error */ + else { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + /* if the next char is '.' followed by a digit then read all float digits */ + if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') { + is_float = 1; + idx += 2; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + + /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */ + if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) { + + /* save the index of the 'e' or 'E' just in case we need to backtrack */ + Py_ssize_t e_start = idx; + idx++; + + /* read an exponent sign if present */ + if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++; + + /* read all digits */ + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + + /* if we got a digit, then parse as float. if not, backtrack */ + if (str[idx - 1] >= '0' && str[idx - 1] <= '9') { + is_float = 1; + } + else { + idx = e_start; + } + } + + /* copy the section we determined to be a number */ + numstr = PyString_FromStringAndSize(&str[start], idx - start); + if (numstr == NULL) + return NULL; + if (is_float) { + /* parse as a float using a fast path if available, otherwise call user defined method */ + if (s->parse_float != (PyObject *)&PyFloat_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL); + } + else { + rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr))); + } + } + else { + /* parse as an int using a fast path if available, otherwise call user defined method */ + if (s->parse_int != (PyObject *)&PyInt_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL); + } + else { + rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10); + } + } + Py_DECREF(numstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +_match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) { + /* Read a JSON number from PyUnicode pystr. + idx is the index of the first character of the number + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of that number: + PyInt, PyLong, or PyFloat. + May return other types if parse_int or parse_float are set + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + Py_ssize_t idx = start; + int is_float = 0; + PyObject *rval; + PyObject *numstr; + + /* read a sign if it's there, make sure it's not the end of the string */ + if (str[idx] == '-') { + idx++; + if (idx > end_idx) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + } + + /* read as many integer digits as we find as long as it doesn't start with 0 */ + if (str[idx] >= '1' && str[idx] <= '9') { + idx++; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + /* if it starts with 0 we only expect one integer digit */ + else if (str[idx] == '0') { + idx++; + } + /* no integer digits, error */ + else { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + /* if the next char is '.' followed by a digit then read all float digits */ + if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') { + is_float = 1; + idx += 2; + while (idx < end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + + /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */ + if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) { + Py_ssize_t e_start = idx; + idx++; + + /* read an exponent sign if present */ + if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++; + + /* read all digits */ + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + + /* if we got a digit, then parse as float. if not, backtrack */ + if (str[idx - 1] >= '0' && str[idx - 1] <= '9') { + is_float = 1; + } + else { + idx = e_start; + } + } + + /* copy the section we determined to be a number */ + numstr = PyUnicode_FromUnicode(&str[start], idx - start); + if (numstr == NULL) + return NULL; + if (is_float) { + /* parse as a float using a fast path if available, otherwise call user defined method */ + if (s->parse_float != (PyObject *)&PyFloat_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL); + } + else { + rval = PyFloat_FromString(numstr, NULL); + } + } + else { + /* no fast path for unicode -> int, just call */ + rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL); + } + Py_DECREF(numstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) +{ + /* Read one JSON term (of any kind) from PyString pystr. + idx is the index of the first character of the term + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of the term. + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t length = PyString_GET_SIZE(pystr); + if (idx >= length) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + switch (str[idx]) { + case '"': + /* string */ + return scanstring_str(pystr, idx + 1, + PyString_AS_STRING(s->encoding), + PyObject_IsTrue(s->strict), + next_idx_ptr); + case '{': + /* object */ + return _parse_object_str(s, pystr, idx + 1, next_idx_ptr); + case '[': + /* array */ + return _parse_array_str(s, pystr, idx + 1, next_idx_ptr); + case 'n': + /* null */ + if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') { + Py_INCREF(Py_None); + *next_idx_ptr = idx + 4; + return Py_None; + } + break; + case 't': + /* true */ + if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') { + Py_INCREF(Py_True); + *next_idx_ptr = idx + 4; + return Py_True; + } + break; + case 'f': + /* false */ + if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') { + Py_INCREF(Py_False); + *next_idx_ptr = idx + 5; + return Py_False; + } + break; + case 'N': + /* NaN */ + if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') { + return _parse_constant(s, "NaN", idx, next_idx_ptr); + } + break; + case 'I': + /* Infinity */ + if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') { + return _parse_constant(s, "Infinity", idx, next_idx_ptr); + } + break; + case '-': + /* -Infinity */ + if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') { + return _parse_constant(s, "-Infinity", idx, next_idx_ptr); + } + break; + } + /* Didn't find a string, object, array, or named constant. Look for a number. */ + return _match_number_str(s, pystr, idx, next_idx_ptr); +} + +static PyObject * +scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) +{ + /* Read one JSON term (of any kind) from PyUnicode pystr. + idx is the index of the first character of the term + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of the term. + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t length = PyUnicode_GET_SIZE(pystr); + if (idx >= length) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + switch (str[idx]) { + case '"': + /* string */ + return scanstring_unicode(pystr, idx + 1, + PyObject_IsTrue(s->strict), + next_idx_ptr); + case '{': + /* object */ + return _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr); + case '[': + /* array */ + return _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr); + case 'n': + /* null */ + if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') { + Py_INCREF(Py_None); + *next_idx_ptr = idx + 4; + return Py_None; + } + break; + case 't': + /* true */ + if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') { + Py_INCREF(Py_True); + *next_idx_ptr = idx + 4; + return Py_True; + } + break; + case 'f': + /* false */ + if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') { + Py_INCREF(Py_False); + *next_idx_ptr = idx + 5; + return Py_False; + } + break; + case 'N': + /* NaN */ + if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') { + return _parse_constant(s, "NaN", idx, next_idx_ptr); + } + break; + case 'I': + /* Infinity */ + if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') { + return _parse_constant(s, "Infinity", idx, next_idx_ptr); + } + break; + case '-': + /* -Infinity */ + if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') { + return _parse_constant(s, "-Infinity", idx, next_idx_ptr); + } + break; + } + /* Didn't find a string, object, array, or named constant. Look for a number. */ + return _match_number_unicode(s, pystr, idx, next_idx_ptr); +} + +static PyObject * +scanner_call(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Python callable interface to scan_once_{str,unicode} */ + PyObject *pystr; + PyObject *rval; + Py_ssize_t idx; + Py_ssize_t next_idx = -1; + static char *kwlist[] = {"string", "idx", NULL}; + PyScannerObject *s; + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:scan_once", kwlist, &pystr, _convertPyInt_AsSsize_t, &idx)) + return NULL; + + if (PyString_Check(pystr)) { + rval = scan_once_str(s, pystr, idx, &next_idx); + } + else if (PyUnicode_Check(pystr)) { + rval = scan_once_unicode(s, pystr, idx, &next_idx); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } + return _build_rval_index_tuple(rval, next_idx); +} + +static PyObject * +scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyScannerObject *s; + s = (PyScannerObject *)type->tp_alloc(type, 0); + if (s != NULL) { + s->encoding = NULL; + s->strict = NULL; + s->object_hook = NULL; + s->parse_float = NULL; + s->parse_int = NULL; + s->parse_constant = NULL; + } + return (PyObject *)s; +} + +static int +scanner_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Initialize Scanner object */ + PyObject *ctx; + static char *kwlist[] = {"context", NULL}; + PyScannerObject *s; + + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:make_scanner", kwlist, &ctx)) + return -1; + + /* PyString_AS_STRING is used on encoding */ + s->encoding = PyObject_GetAttrString(ctx, "encoding"); + if (s->encoding == Py_None) { + Py_DECREF(Py_None); + s->encoding = PyString_InternFromString(DEFAULT_ENCODING); + } + else if (PyUnicode_Check(s->encoding)) { + PyObject *tmp = PyUnicode_AsEncodedString(s->encoding, NULL, NULL); + Py_DECREF(s->encoding); + s->encoding = tmp; + } + if (s->encoding == NULL || !PyString_Check(s->encoding)) + goto bail; + + /* All of these will fail "gracefully" so we don't need to verify them */ + s->strict = PyObject_GetAttrString(ctx, "strict"); + if (s->strict == NULL) + goto bail; + s->object_hook = PyObject_GetAttrString(ctx, "object_hook"); + if (s->object_hook == NULL) + goto bail; + s->parse_float = PyObject_GetAttrString(ctx, "parse_float"); + if (s->parse_float == NULL) + goto bail; + s->parse_int = PyObject_GetAttrString(ctx, "parse_int"); + if (s->parse_int == NULL) + goto bail; + s->parse_constant = PyObject_GetAttrString(ctx, "parse_constant"); + if (s->parse_constant == NULL) + goto bail; + + return 0; + +bail: + Py_CLEAR(s->encoding); + Py_CLEAR(s->strict); + Py_CLEAR(s->object_hook); + Py_CLEAR(s->parse_float); + Py_CLEAR(s->parse_int); + Py_CLEAR(s->parse_constant); + return -1; +} + +PyDoc_STRVAR(scanner_doc, "JSON scanner object"); + +static +PyTypeObject PyScannerType = { + PyObject_HEAD_INIT(NULL) + 0, /* tp_internal */ + "simplejson._speedups.Scanner", /* tp_name */ + sizeof(PyScannerObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + scanner_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + scanner_call, /* tp_call */ + 0, /* tp_str */ + 0,/* PyObject_GenericGetAttr, */ /* tp_getattro */ + 0,/* PyObject_GenericSetAttr, */ /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + scanner_doc, /* tp_doc */ + scanner_traverse, /* tp_traverse */ + scanner_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + scanner_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + scanner_init, /* tp_init */ + 0,/* PyType_GenericAlloc, */ /* tp_alloc */ + scanner_new, /* tp_new */ + 0,/* PyObject_GC_Del, */ /* tp_free */ +}; + +static PyObject * +encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyEncoderObject *s; + s = (PyEncoderObject *)type->tp_alloc(type, 0); + if (s != NULL) { + s->markers = NULL; + s->defaultfn = NULL; + s->encoder = NULL; + s->indent = NULL; + s->key_separator = NULL; + s->item_separator = NULL; + s->sort_keys = NULL; + s->skipkeys = NULL; + } + return (PyObject *)s; +} + +static int +encoder_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* initialize Encoder object */ + static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL}; + + PyEncoderObject *s; + PyObject *allow_nan; + + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOO:make_encoder", kwlist, + &s->markers, &s->defaultfn, &s->encoder, &s->indent, &s->key_separator, &s->item_separator, &s->sort_keys, &s->skipkeys, &allow_nan)) + return -1; + + Py_INCREF(s->markers); + Py_INCREF(s->defaultfn); + Py_INCREF(s->encoder); + Py_INCREF(s->indent); + Py_INCREF(s->key_separator); + Py_INCREF(s->item_separator); + Py_INCREF(s->sort_keys); + Py_INCREF(s->skipkeys); + s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii); + s->allow_nan = PyObject_IsTrue(allow_nan); + return 0; +} + +static PyObject * +encoder_call(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Python callable interface to encode_listencode_obj */ + static char *kwlist[] = {"obj", "_current_indent_level", NULL}; + PyObject *obj; + PyObject *rval; + Py_ssize_t indent_level; + PyEncoderObject *s; + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:_iterencode", kwlist, + &obj, _convertPyInt_AsSsize_t, &indent_level)) + return NULL; + rval = PyList_New(0); + if (rval == NULL) + return NULL; + if (encoder_listencode_obj(s, rval, obj, indent_level)) { + Py_DECREF(rval); + return NULL; + } + return rval; +} + +static PyObject * +_encoded_const(PyObject *obj) +{ + /* Return the JSON string representation of None, True, False */ + if (obj == Py_None) { + static PyObject *s_null = NULL; + if (s_null == NULL) { + s_null = PyString_InternFromString("null"); + } + Py_INCREF(s_null); + return s_null; + } + else if (obj == Py_True) { + static PyObject *s_true = NULL; + if (s_true == NULL) { + s_true = PyString_InternFromString("true"); + } + Py_INCREF(s_true); + return s_true; + } + else if (obj == Py_False) { + static PyObject *s_false = NULL; + if (s_false == NULL) { + s_false = PyString_InternFromString("false"); + } + Py_INCREF(s_false); + return s_false; + } + else { + PyErr_SetString(PyExc_ValueError, "not a const"); + return NULL; + } +} + +static PyObject * +encoder_encode_float(PyEncoderObject *s, PyObject *obj) +{ + /* Return the JSON representation of a PyFloat */ + double i = PyFloat_AS_DOUBLE(obj); + if (!Py_IS_FINITE(i)) { + if (!s->allow_nan) { + PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant"); + return NULL; + } + if (i > 0) { + return PyString_FromString("Infinity"); + } + else if (i < 0) { + return PyString_FromString("-Infinity"); + } + else { + return PyString_FromString("NaN"); + } + } + /* Use a better float format here? */ + return PyObject_Repr(obj); +} + +static PyObject * +encoder_encode_string(PyEncoderObject *s, PyObject *obj) +{ + /* Return the JSON representation of a string */ + if (s->fast_encode) + return py_encode_basestring_ascii(NULL, obj); + else + return PyObject_CallFunctionObjArgs(s->encoder, obj, NULL); +} + +static int +_steal_list_append(PyObject *lst, PyObject *stolen) +{ + /* Append stolen and then decrement its reference count */ + int rval = PyList_Append(lst, stolen); + Py_DECREF(stolen); + return rval; +} + +static int +encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level) +{ + /* Encode Python object obj to a JSON term, rval is a PyList */ + PyObject *newobj; + int rv; + + if (obj == Py_None || obj == Py_True || obj == Py_False) { + PyObject *cstr = _encoded_const(obj); + if (cstr == NULL) + return -1; + return _steal_list_append(rval, cstr); + } + else if (PyString_Check(obj) || PyUnicode_Check(obj)) + { + PyObject *encoded = encoder_encode_string(s, obj); + if (encoded == NULL) + return -1; + return _steal_list_append(rval, encoded); + } + else if (PyInt_Check(obj) || PyLong_Check(obj)) { + PyObject *encoded = PyObject_Str(obj); + if (encoded == NULL) + return -1; + return _steal_list_append(rval, encoded); + } + else if (PyFloat_Check(obj)) { + PyObject *encoded = encoder_encode_float(s, obj); + if (encoded == NULL) + return -1; + return _steal_list_append(rval, encoded); + } + else if (PyList_Check(obj) || PyTuple_Check(obj)) { + return encoder_listencode_list(s, rval, obj, indent_level); + } + else if (PyDict_Check(obj)) { + return encoder_listencode_dict(s, rval, obj, indent_level); + } + else { + PyObject *ident = NULL; + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(obj); + if (ident == NULL) + return -1; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + Py_DECREF(ident); + return -1; + } + if (PyDict_SetItem(s->markers, ident, obj)) { + Py_DECREF(ident); + return -1; + } + } + newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL); + if (newobj == NULL) { + Py_XDECREF(ident); + return -1; + } + rv = encoder_listencode_obj(s, rval, newobj, indent_level); + Py_DECREF(newobj); + if (rv) { + Py_XDECREF(ident); + return -1; + } + if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) { + Py_XDECREF(ident); + return -1; + } + Py_XDECREF(ident); + } + return rv; + } +} + +static int +encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level) +{ + /* Encode Python dict dct a JSON term, rval is a PyList */ + static PyObject *open_dict = NULL; + static PyObject *close_dict = NULL; + static PyObject *empty_dict = NULL; + PyObject *kstr = NULL; + PyObject *ident = NULL; + PyObject *key, *value; + Py_ssize_t pos; + int skipkeys; + Py_ssize_t idx; + + if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) { + open_dict = PyString_InternFromString("{"); + close_dict = PyString_InternFromString("}"); + empty_dict = PyString_InternFromString("{}"); + if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) + return -1; + } + if (PyDict_Size(dct) == 0) + return PyList_Append(rval, empty_dict); + + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(dct); + if (ident == NULL) + goto bail; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + goto bail; + } + if (PyDict_SetItem(s->markers, ident, dct)) { + goto bail; + } + } + + if (PyList_Append(rval, open_dict)) + goto bail; + + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level += 1; + /* + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + separator = _item_separator + newline_indent + buf += newline_indent + */ + } + + /* TODO: C speedup not implemented for sort_keys */ + + pos = 0; + skipkeys = PyObject_IsTrue(s->skipkeys); + idx = 0; + while (PyDict_Next(dct, &pos, &key, &value)) { + PyObject *encoded; + + if (PyString_Check(key) || PyUnicode_Check(key)) { + Py_INCREF(key); + kstr = key; + } + else if (PyFloat_Check(key)) { + kstr = encoder_encode_float(s, key); + if (kstr == NULL) + goto bail; + } + else if (PyInt_Check(key) || PyLong_Check(key)) { + kstr = PyObject_Str(key); + if (kstr == NULL) + goto bail; + } + else if (key == Py_True || key == Py_False || key == Py_None) { + kstr = _encoded_const(key); + if (kstr == NULL) + goto bail; + } + else if (skipkeys) { + continue; + } + else { + /* TODO: include repr of key */ + PyErr_SetString(PyExc_ValueError, "keys must be a string"); + goto bail; + } + + if (idx) { + if (PyList_Append(rval, s->item_separator)) + goto bail; + } + + encoded = encoder_encode_string(s, kstr); + Py_CLEAR(kstr); + if (encoded == NULL) + goto bail; + if (PyList_Append(rval, encoded)) { + Py_DECREF(encoded); + goto bail; + } + Py_DECREF(encoded); + if (PyList_Append(rval, s->key_separator)) + goto bail; + if (encoder_listencode_obj(s, rval, value, indent_level)) + goto bail; + idx += 1; + } + if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) + goto bail; + Py_CLEAR(ident); + } + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level -= 1; + /* + yield '\n' + (' ' * (_indent * _current_indent_level)) + */ + } + if (PyList_Append(rval, close_dict)) + goto bail; + return 0; + +bail: + Py_XDECREF(kstr); + Py_XDECREF(ident); + return -1; +} + + +static int +encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level) +{ + /* Encode Python list seq to a JSON term, rval is a PyList */ + static PyObject *open_array = NULL; + static PyObject *close_array = NULL; + static PyObject *empty_array = NULL; + PyObject *ident = NULL; + PyObject *s_fast = NULL; + Py_ssize_t num_items; + PyObject **seq_items; + Py_ssize_t i; + + if (open_array == NULL || close_array == NULL || empty_array == NULL) { + open_array = PyString_InternFromString("["); + close_array = PyString_InternFromString("]"); + empty_array = PyString_InternFromString("[]"); + if (open_array == NULL || close_array == NULL || empty_array == NULL) + return -1; + } + ident = NULL; + s_fast = PySequence_Fast(seq, "_iterencode_list needs a sequence"); + if (s_fast == NULL) + return -1; + num_items = PySequence_Fast_GET_SIZE(s_fast); + if (num_items == 0) { + Py_DECREF(s_fast); + return PyList_Append(rval, empty_array); + } + + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(seq); + if (ident == NULL) + goto bail; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + goto bail; + } + if (PyDict_SetItem(s->markers, ident, seq)) { + goto bail; + } + } + + seq_items = PySequence_Fast_ITEMS(s_fast); + if (PyList_Append(rval, open_array)) + goto bail; + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level += 1; + /* + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + separator = _item_separator + newline_indent + buf += newline_indent + */ + } + for (i = 0; i < num_items; i++) { + PyObject *obj = seq_items[i]; + if (i) { + if (PyList_Append(rval, s->item_separator)) + goto bail; + } + if (encoder_listencode_obj(s, rval, obj, indent_level)) + goto bail; + } + if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) + goto bail; + Py_CLEAR(ident); + } + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level -= 1; + /* + yield '\n' + (' ' * (_indent * _current_indent_level)) + */ + } + if (PyList_Append(rval, close_array)) + goto bail; + Py_DECREF(s_fast); + return 0; + +bail: + Py_XDECREF(ident); + Py_DECREF(s_fast); + return -1; +} + +static void +encoder_dealloc(PyObject *self) +{ + /* Deallocate Encoder */ + encoder_clear(self); + Py_TYPE(self)->tp_free(self); +} + +static int +encoder_traverse(PyObject *self, visitproc visit, void *arg) +{ + PyEncoderObject *s; + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + Py_VISIT(s->markers); + Py_VISIT(s->defaultfn); + Py_VISIT(s->encoder); + Py_VISIT(s->indent); + Py_VISIT(s->key_separator); + Py_VISIT(s->item_separator); + Py_VISIT(s->sort_keys); + Py_VISIT(s->skipkeys); + return 0; +} + +static int +encoder_clear(PyObject *self) +{ + /* Deallocate Encoder */ + PyEncoderObject *s; + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + Py_CLEAR(s->markers); + Py_CLEAR(s->defaultfn); + Py_CLEAR(s->encoder); + Py_CLEAR(s->indent); + Py_CLEAR(s->key_separator); + Py_CLEAR(s->item_separator); + Py_CLEAR(s->sort_keys); + Py_CLEAR(s->skipkeys); + return 0; +} + +PyDoc_STRVAR(encoder_doc, "_iterencode(obj, _current_indent_level) -> iterable"); + +static +PyTypeObject PyEncoderType = { + PyObject_HEAD_INIT(NULL) + 0, /* tp_internal */ + "simplejson._speedups.Encoder", /* tp_name */ + sizeof(PyEncoderObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + encoder_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + encoder_call, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + encoder_doc, /* tp_doc */ + encoder_traverse, /* tp_traverse */ + encoder_clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + encoder_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + encoder_init, /* tp_init */ + 0, /* tp_alloc */ + encoder_new, /* tp_new */ + 0, /* tp_free */ +}; + +static PyMethodDef speedups_methods[] = { + {"encode_basestring_ascii", + (PyCFunction)py_encode_basestring_ascii, + METH_O, + pydoc_encode_basestring_ascii}, + {"scanstring", + (PyCFunction)py_scanstring, + METH_VARARGS, + pydoc_scanstring}, + {NULL, NULL, 0, NULL} +}; + +PyDoc_STRVAR(module_doc, +"simplejson speedups\n"); + +void +init_speedups(void) +{ + PyObject *m; + PyScannerType.tp_new = PyType_GenericNew; + if (PyType_Ready(&PyScannerType) < 0) + return; + PyEncoderType.tp_new = PyType_GenericNew; + if (PyType_Ready(&PyEncoderType) < 0) + return; + m = Py_InitModule3("_speedups", speedups_methods, module_doc); + Py_INCREF((PyObject*)&PyScannerType); + PyModule_AddObject(m, "make_scanner", (PyObject*)&PyScannerType); + Py_INCREF((PyObject*)&PyEncoderType); + PyModule_AddObject(m, "make_encoder", (PyObject*)&PyEncoderType); +} diff --git a/pyulib/src/ulib/ext/simplejson/decoder.py b/pyulib/src/ulib/ext/simplejson/decoder.py new file mode 100644 index 0000000..811e733 --- /dev/null +++ b/pyulib/src/ulib/ext/simplejson/decoder.py @@ -0,0 +1,354 @@ +"""Implementation of JSONDecoder +""" +import re +import sys +import struct + +from scanner import make_scanner +try: + from _speedups import scanstring as c_scanstring +except ImportError: + c_scanstring = None + +__all__ = ['JSONDecoder'] + +FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL + +def _floatconstants(): + _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') + if sys.byteorder != 'big': + _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] + nan, inf = struct.unpack('dd', _BYTES) + return nan, inf, -inf + +NaN, PosInf, NegInf = _floatconstants() + + +def linecol(doc, pos): + lineno = doc.count('\n', 0, pos) + 1 + if lineno == 1: + colno = pos + else: + colno = pos - doc.rindex('\n', 0, pos) + return lineno, colno + + +def errmsg(msg, doc, pos, end=None): + # Note that this function is called from _speedups + lineno, colno = linecol(doc, pos) + if end is None: + #fmt = '{0}: line {1} column {2} (char {3})' + #return fmt.format(msg, lineno, colno, pos) + fmt = '%s: line %d column %d (char %d)' + return fmt % (msg, lineno, colno, pos) + endlineno, endcolno = linecol(doc, end) + #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})' + #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end) + fmt = '%s: line %d column %d - line %d column %d (char %d - %d)' + return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end) + + +_CONSTANTS = { + '-Infinity': NegInf, + 'Infinity': PosInf, + 'NaN': NaN, +} + +STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) +BACKSLASH = { + '"': u'"', '\\': u'\\', '/': u'/', + 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', +} + +DEFAULT_ENCODING = "utf-8" + +def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match): + """Scan the string s for a JSON string. End is the index of the + character in s after the quote that started the JSON string. + Unescapes all valid JSON string escape sequences and raises ValueError + on attempt to decode an invalid string. If strict is False then literal + control characters are allowed in the string. + + Returns a tuple of the decoded string and the index of the character in s + after the end quote.""" + if encoding is None: + encoding = DEFAULT_ENCODING + chunks = [] + _append = chunks.append + begin = end - 1 + while 1: + chunk = _m(s, end) + if chunk is None: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + end = chunk.end() + content, terminator = chunk.groups() + # Content is contains zero or more unescaped string characters + if content: + if not isinstance(content, unicode): + content = unicode(content, encoding) + _append(content) + # Terminator is the end of string, a literal control character, + # or a backslash denoting that an escape sequence follows + if terminator == '"': + break + elif terminator != '\\': + if strict: + msg = "Invalid control character %r at" % (terminator,) + #msg = "Invalid control character {0!r} at".format(terminator) + raise ValueError(errmsg(msg, s, end)) + else: + _append(terminator) + continue + try: + esc = s[end] + except IndexError: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + # If not a unicode escape sequence, must be in the lookup table + if esc != 'u': + try: + char = _b[esc] + except KeyError: + msg = "Invalid \\escape: " + repr(esc) + raise ValueError(errmsg(msg, s, end)) + end += 1 + else: + # Unicode escape sequence + esc = s[end + 1:end + 5] + next_end = end + 5 + if len(esc) != 4: + msg = "Invalid \\uXXXX escape" + raise ValueError(errmsg(msg, s, end)) + uni = int(esc, 16) + # Check for surrogate pair on UCS-4 systems + if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: + msg = "Invalid \\uXXXX\\uXXXX surrogate pair" + if not s[end + 5:end + 7] == '\\u': + raise ValueError(errmsg(msg, s, end)) + esc2 = s[end + 7:end + 11] + if len(esc2) != 4: + raise ValueError(errmsg(msg, s, end)) + uni2 = int(esc2, 16) + uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) + next_end += 6 + char = unichr(uni) + end = next_end + # Append the unescaped character + _append(char) + return u''.join(chunks), end + + +# Use speedup if available +scanstring = c_scanstring or py_scanstring + +WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) +WHITESPACE_STR = ' \t\n\r' + +def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + pairs = {} + # Use a slice to prevent IndexError from being raised, the following + # check will raise a more specific ValueError if the string is empty + nextchar = s[end:end + 1] + # Normally we expect nextchar == '"' + if nextchar != '"': + if nextchar in _ws: + end = _w(s, end).end() + nextchar = s[end:end + 1] + # Trivial empty object + if nextchar == '}': + return pairs, end + 1 + elif nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end)) + end += 1 + while True: + key, end = scanstring(s, end, encoding, strict) + + # To skip some function call overhead we optimize the fast paths where + # the JSON key separator is ": " or just ":". + if s[end:end + 1] != ':': + end = _w(s, end).end() + if s[end:end + 1] != ':': + raise ValueError(errmsg("Expecting : delimiter", s, end)) + + end += 1 + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + try: + value, end = scan_once(s, end) + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + pairs[key] = value + + try: + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + end += 1 + + if nextchar == '}': + break + elif nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) + + try: + nextchar = s[end] + if nextchar in _ws: + end += 1 + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + + end += 1 + if nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end - 1)) + + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end + +def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + values = [] + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + # Look-ahead for trivial empty array + if nextchar == ']': + return values, end + 1 + _append = values.append + while True: + try: + value, end = scan_once(s, end) + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + _append(value) + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == ']': + break + elif nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end)) + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + return values, end + +class JSONDecoder(object): + """Simple JSON decoder + + Performs the following translations in decoding by default: + + +---------------+-------------------+ + | JSON | Python | + +===============+===================+ + | object | dict | + +---------------+-------------------+ + | array | list | + +---------------+-------------------+ + | string | unicode | + +---------------+-------------------+ + | number (int) | int, long | + +---------------+-------------------+ + | number (real) | float | + +---------------+-------------------+ + | true | True | + +---------------+-------------------+ + | false | False | + +---------------+-------------------+ + | null | None | + +---------------+-------------------+ + + It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as + their corresponding ``float`` values, which is outside the JSON spec. + + """ + + def __init__(self, encoding=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, strict=True): + """``encoding`` determines the encoding used to interpret any ``str`` + objects decoded by this instance (utf-8 by default). It has no + effect when decoding ``unicode`` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as ``unicode``. + + ``object_hook``, if specified, will be called with the result + of every JSON object decoded and its return value will be used in + place of the given ``dict``. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN. + This can be used to raise an exception if invalid JSON numbers + are encountered. + + """ + self.encoding = encoding + self.object_hook = object_hook + self.parse_float = parse_float or float + self.parse_int = parse_int or int + self.parse_constant = parse_constant or _CONSTANTS.__getitem__ + self.strict = strict + self.parse_object = JSONObject + self.parse_array = JSONArray + self.parse_string = scanstring + self.scan_once = make_scanner(self) + + def decode(self, s, _w=WHITESPACE.match): + """Return the Python representation of ``s`` (a ``str`` or ``unicode`` + instance containing a JSON document) + + """ + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + end = _w(s, end).end() + if end != len(s): + raise ValueError(errmsg("Extra data", s, end, len(s))) + return obj + + def raw_decode(self, s, idx=0): + """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning + with a JSON document) and return a 2-tuple of the Python + representation and the index in ``s`` where the document ended. + + This can be used to decode a JSON document from a string that may + have extraneous data at the end. + + """ + try: + obj, end = self.scan_once(s, idx) + except StopIteration: + raise ValueError("No JSON object could be decoded") + return obj, end diff --git a/pyulib/src/ulib/ext/simplejson/decoder.pyc b/pyulib/src/ulib/ext/simplejson/decoder.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42bdfaaa54936d12bd08e856912aec7d69a2c21e GIT binary patch literal 12973 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 10) & 0x3ff) + s2 = 0xdc00 | (n & 0x3ff) + #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2) + return '\\u%04x\\u%04x' % (s1, s2) + return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' + + +encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii + +class JSONEncoder(object): + """Extensible JSON encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + + """ + item_separator = ', ' + key_separator = ': ' + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False, + indent=None, separators=None, encoding='utf-8', default=None): + """Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is false, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If ensure_ascii is true, the output is guaranteed to be str + objects with all incoming unicode characters escaped. If + ensure_ascii is false, the output will be unicode object. + + If check_circular is true, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is true, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is true, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + + If indent is a non-negative integer, then JSON array + elements and object members will be pretty-printed with that + indent level. An indent level of 0 will only insert newlines. + None is the most compact representation. + + If specified, separators should be a (item_separator, key_separator) + tuple. The default is (', ', ': '). To get the most compact JSON + representation you should specify (',', ':') to eliminate whitespace. + + If specified, default is a function that gets called for objects + that can't otherwise be serialized. It should return a JSON encodable + version of the object or raise a ``TypeError``. + + If encoding is not None, then all input strings will be + transformed into unicode using that encoding prior to JSON-encoding. + The default is UTF-8. + + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + self.indent = indent + if separators is not None: + self.item_separator, self.key_separator = separators + if default is not None: + self.default = default + self.encoding = encoding + + def default(self, o): + """Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + + """ + raise TypeError(repr(o) + " is not JSON serializable") + + def encode(self, o): + """Return a JSON string representation of a Python data structure. + + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo": ["bar", "baz"]}' + + """ + # This is for extremely simple cases and benchmarks. + if isinstance(o, basestring): + if isinstance(o, str): + _encoding = self.encoding + if (_encoding is not None + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + if self.ensure_ascii: + return encode_basestring_ascii(o) + else: + return encode_basestring(o) + # This doesn't pass the iterator directly to ''.join() because the + # exceptions aren't as detailed. The list call should be roughly + # equivalent to the PySequence_Fast that ''.join() would do. + chunks = self.iterencode(o, _one_shot=True) + if not isinstance(chunks, (list, tuple)): + chunks = list(chunks) + return ''.join(chunks) + + def iterencode(self, o, _one_shot=False): + """Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + + """ + if self.check_circular: + markers = {} + else: + markers = None + if self.ensure_ascii: + _encoder = encode_basestring_ascii + else: + _encoder = encode_basestring + if self.encoding != 'utf-8': + def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): + if isinstance(o, str): + o = o.decode(_encoding) + return _orig_encoder(o) + + def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY): + # Check for specials. Note that this type of test is processor- and/or + # platform-specific, so do tests which don't depend on the internals. + + if o != o: + text = 'NaN' + elif o == _inf: + text = 'Infinity' + elif o == _neginf: + text = '-Infinity' + else: + return _repr(o) + + if not allow_nan: + raise ValueError( + "Out of range float values are not JSON compliant: " + + repr(o)) + + return text + + + if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys: + _iterencode = c_make_encoder( + markers, self.default, _encoder, self.indent, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, self.allow_nan) + else: + _iterencode = _make_iterencode( + markers, self.default, _encoder, self.indent, floatstr, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, _one_shot) + return _iterencode(o, 0) + +def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot, + ## HACK: hand-optimized bytecode; turn globals into locals + False=False, + True=True, + ValueError=ValueError, + basestring=basestring, + dict=dict, + float=float, + id=id, + int=int, + isinstance=isinstance, + list=list, + long=long, + str=str, + tuple=tuple, + ): + + def _iterencode_list(lst, _current_indent_level): + if not lst: + yield '[]' + return + if markers is not None: + markerid = id(lst) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = lst + buf = '[' + if _indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + separator = _item_separator + newline_indent + buf += newline_indent + else: + newline_indent = None + separator = _item_separator + first = True + for value in lst: + if first: + first = False + else: + buf = separator + if isinstance(value, basestring): + yield buf + _encoder(value) + elif value is None: + yield buf + 'null' + elif value is True: + yield buf + 'true' + elif value is False: + yield buf + 'false' + elif isinstance(value, (int, long)): + yield buf + str(value) + elif isinstance(value, float): + yield buf + _floatstr(value) + else: + yield buf + if isinstance(value, (list, tuple)): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (' ' * (_indent * _current_indent_level)) + yield ']' + if markers is not None: + del markers[markerid] + + def _iterencode_dict(dct, _current_indent_level): + if not dct: + yield '{}' + return + if markers is not None: + markerid = id(dct) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = dct + yield '{' + if _indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + item_separator = _item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + item_separator = _item_separator + first = True + if _sort_keys: + items = dct.items() + items.sort(key=lambda kv: kv[0]) + else: + items = dct.iteritems() + for key, value in items: + if isinstance(key, basestring): + pass + # JavaScript is weakly typed for these, so it makes sense to + # also allow them. Many encoders seem to do something like this. + elif isinstance(key, float): + key = _floatstr(key) + elif key is True: + key = 'true' + elif key is False: + key = 'false' + elif key is None: + key = 'null' + elif isinstance(key, (int, long)): + key = str(key) + elif _skipkeys: + continue + else: + raise TypeError("key " + repr(key) + " is not a string") + if first: + first = False + else: + yield item_separator + yield _encoder(key) + yield _key_separator + if isinstance(value, basestring): + yield _encoder(value) + elif value is None: + yield 'null' + elif value is True: + yield 'true' + elif value is False: + yield 'false' + elif isinstance(value, (int, long)): + yield str(value) + elif isinstance(value, float): + yield _floatstr(value) + else: + if isinstance(value, (list, tuple)): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (' ' * (_indent * _current_indent_level)) + yield '}' + if markers is not None: + del markers[markerid] + + def _iterencode(o, _current_indent_level): + if isinstance(o, basestring): + yield _encoder(o) + elif o is None: + yield 'null' + elif o is True: + yield 'true' + elif o is False: + yield 'false' + elif isinstance(o, (int, long)): + yield str(o) + elif isinstance(o, float): + yield _floatstr(o) + elif isinstance(o, (list, tuple)): + for chunk in _iterencode_list(o, _current_indent_level): + yield chunk + elif isinstance(o, dict): + for chunk in _iterencode_dict(o, _current_indent_level): + yield chunk + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + o = _default(o) + for chunk in _iterencode(o, _current_indent_level): + yield chunk + if markers is not None: + del markers[markerid] + + return _iterencode diff --git a/pyulib/src/ulib/ext/simplejson/encoder.pyc b/pyulib/src/ulib/ext/simplejson/encoder.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6615f0bb917c4bea381958072ec07b7f6a425124 GIT binary patch literal 17660 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 bG^aPnt>h+>JXR+o=6M|1mTld&?2rBd07l^} literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/tarfile/README.txt b/pyulib/src/ulib/ext/tarfile/README.txt new file mode 100644 index 0000000..0e31214 --- /dev/null +++ b/pyulib/src/ulib/ext/tarfile/README.txt @@ -0,0 +1,4 @@ +Les fichiers de ce rpertoire ont t rcupr de la distribution +tarfile-0.7.2 pris sur http://www.gustaebel.de/lars/tarfile/ + +Les fichiers ont t patchs pour le support python 2.x diff --git a/pyulib/src/ulib/ext/tarfile/__init__.py b/pyulib/src/ulib/ext/tarfile/__init__.py new file mode 100644 index 0000000..5a00fb6 --- /dev/null +++ b/pyulib/src/ulib/ext/tarfile/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 diff --git a/pyulib/src/ulib/ext/tarfile/gzip22.py b/pyulib/src/ulib/ext/tarfile/gzip22.py new file mode 100644 index 0000000..63aa245 --- /dev/null +++ b/pyulib/src/ulib/ext/tarfile/gzip22.py @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +"""Functions that read and write gzipped files. + +The user of the file doesn't have to worry about the compression, +but random access is not allowed.""" + +# based on Andrew Kuchling's minigzip.py distributed with the zlib module + +import struct, sys, time +import zlib +import __builtin__ + +__all__ = ["GzipFile","open"] + +FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16 + +READ, WRITE = 1, 2 + +def write32(output, value): + output.write(struct.pack("' + + def _init_write(self, filename): + if filename[-3:] != '.gz': + filename = filename + '.gz' + self.filename = filename + self.crc = zlib.crc32("") + self.size = 0 + self.writebuf = [] + self.bufsize = 0 + + def _write_gzip_header(self): + self.fileobj.write('\037\213') # magic header + self.fileobj.write('\010') # compression method + fname = self.filename[:-3] + flags = 0 + if fname: + flags = FNAME + self.fileobj.write(chr(flags)) + write32u(self.fileobj, long(time.time())) + self.fileobj.write('\002') + self.fileobj.write('\377') + if fname: + self.fileobj.write(fname + '\000') + + def _init_read(self): + self.crc = zlib.crc32("") + self.size = 0 + + def _read_gzip_header(self): + magic = self.fileobj.read(2) + if magic != '\037\213': + raise IOError, 'Not a gzipped file' + method = ord( self.fileobj.read(1) ) + if method != 8: + raise IOError, 'Unknown compression method' + flag = ord( self.fileobj.read(1) ) + # modtime = self.fileobj.read(4) + # extraflag = self.fileobj.read(1) + # os = self.fileobj.read(1) + self.fileobj.read(6) + + if flag & FEXTRA: + # Read & discard the extra field, if present + xlen=ord(self.fileobj.read(1)) + xlen=xlen+256*ord(self.fileobj.read(1)) + self.fileobj.read(xlen) + if flag & FNAME: + # Read and discard a null-terminated string containing the filename + while (1): + s=self.fileobj.read(1) + if not s or s=='\000': break + if flag & FCOMMENT: + # Read and discard a null-terminated string containing a comment + while (1): + s=self.fileobj.read(1) + if not s or s=='\000': break + if flag & FHCRC: + self.fileobj.read(2) # Read & discard the 16-bit header CRC + + + def write(self,data): + if self.fileobj is None: + raise ValueError, "write() on closed GzipFile object" + if len(data) > 0: + self.size = self.size + len(data) + self.crc = zlib.crc32(data, self.crc) + self.fileobj.write( self.compress.compress(data) ) + self.offset += len(data) + + def read(self, size=-1): + if self.extrasize <= 0 and self.fileobj is None: + return '' + + readsize = 1024 + if size < 0: # get the whole thing + try: + while 1: + self._read(readsize) + readsize = readsize * 2 + except EOFError: + size = self.extrasize + else: # just get some more of it + try: + while size > self.extrasize: + self._read(readsize) + readsize = readsize * 2 + except EOFError: + if size > self.extrasize: + size = self.extrasize + + chunk = self.extrabuf[:size] + self.extrabuf = self.extrabuf[size:] + self.extrasize = self.extrasize - size + + self.offset += size + return chunk + + def _unread(self, buf): + self.extrabuf = buf + self.extrabuf + self.extrasize = len(buf) + self.extrasize + self.offset -= len(buf) + + def _read(self, size=1024): + if self.fileobj is None: raise EOFError, "Reached EOF" + + if self._new_member: + # If the _new_member flag is set, we have to + # jump to the next member, if there is one. + # + # First, check if we're at the end of the file; + # if so, it's time to stop; no more members to read. + pos = self.fileobj.tell() # Save current position + self.fileobj.seek(0, 2) # Seek to end of file + if pos == self.fileobj.tell(): + raise EOFError, "Reached EOF" + else: + self.fileobj.seek( pos ) # Return to original position + + self._init_read() + self._read_gzip_header() + self.decompress = zlib.decompressobj(-zlib.MAX_WBITS) + self._new_member = 0 + + # Read a chunk of data from the file + buf = self.fileobj.read(size) + + # If the EOF has been reached, flush the decompression object + # and mark this object as finished. + + if buf == "": + uncompress = self.decompress.flush() + self._read_eof() + self._add_read_data( uncompress ) + raise EOFError, 'Reached EOF' + + uncompress = self.decompress.decompress(buf) + self._add_read_data( uncompress ) + + if self.decompress.unused_data != "": + # Ending case: we've come to the end of a member in the file, + # so seek back to the start of the unused data, finish up + # this member, and read a new gzip header. + # (The number of bytes to seek back is the length of the unused + # data, minus 8 because _read_eof() will rewind a further 8 bytes) + self.fileobj.seek( -len(self.decompress.unused_data)+8, 1) + + # Check the CRC and file size, and set the flag so we read + # a new member on the next call + self._read_eof() + self._new_member = 1 + + def _add_read_data(self, data): + self.crc = zlib.crc32(data, self.crc) + self.extrabuf = self.extrabuf + data + self.extrasize = self.extrasize + len(data) + self.size = self.size + len(data) + + def _read_eof(self): + # We've read to the end of the file, so we have to rewind in order + # to reread the 8 bytes containing the CRC and the file size. + # We check the that the computed CRC and size of the + # uncompressed data matches the stored values. + self.fileobj.seek(-8, 1) + crc32 = read32(self.fileobj) + isize = read32(self.fileobj) + if crc32%0x100000000L != self.crc%0x100000000L: + raise ValueError, "CRC check failed" + elif isize != self.size: + raise ValueError, "Incorrect length of data produced" + + def close(self): + if self.mode == WRITE: + self.fileobj.write(self.compress.flush()) + write32(self.fileobj, self.crc) + write32(self.fileobj, self.size) + self.fileobj = None + elif self.mode == READ: + self.fileobj = None + if self.myfileobj: + self.myfileobj.close() + self.myfileobj = None + + def __del__(self): + try: + if (self.myfileobj is None and + self.fileobj is None): + return + except AttributeError: + return + self.close() + + def flush(self): + self.fileobj.flush() + + def isatty(self): + return 0 + + def tell(self): + return self.offset + + def rewind(self): + '''Return the uncompressed stream file position indicator to the + beginning of the file''' + if self.mode != READ: + raise IOError("Can't rewind in write mode") + self.fileobj.seek(0) + self._new_member = 1 + self.extrabuf = "" + self.extrasize = 0 + self.offset = 0 + + def seek(self, offset): + if self.mode == WRITE: + if offset < self.offset: + raise IOError('Negative seek in write mode') + count = offset - self.offset + for i in range(count/1024): + self.write(1024*'\0') + self.write((count%1024)*'\0') + elif self.mode == READ: + if offset < self.offset: + # for negative seek, rewind and do positive seek + self.rewind() + count = offset - self.offset + for i in range(count/1024): self.read(1024) + self.read(count % 1024) + + def readline(self, size=-1): + if size < 0: size = sys.maxint + bufs = [] + readsize = min(100, size) # Read from the file in small chunks + while 1: + if size == 0: + return "".join(bufs) # Return resulting line + + c = self.read(readsize) + i = c.find('\n') + if size is not None: + # We set i=size to break out of the loop under two + # conditions: 1) there's no newline, and the chunk is + # larger than size, or 2) there is a newline, but the + # resulting line would be longer than 'size'. + if i==-1 and len(c) > size: i=size-1 + elif size <= i: i = size -1 + + if i >= 0 or c == '': + bufs.append(c[:i+1]) # Add portion of last chunk + self._unread(c[i+1:]) # Push back rest of chunk + return ''.join(bufs) # Return resulting line + + # Append chunk to list, decrease 'size', + bufs.append(c) + size = size - len(c) + readsize = min(size, readsize * 2) + + def readlines(self, sizehint=0): + # Negative numbers result in reading all the lines + if sizehint <= 0: sizehint = sys.maxint + L = [] + while sizehint > 0: + line = self.readline() + if line == "": break + L.append( line ) + sizehint = sizehint - len(line) + + return L + + def writelines(self, L): + for line in L: + self.write(line) + + +def _test(): + # Act like gzip; with -d, act like gunzip. + # The input file is not deleted, however, nor are any other gzip + # options or features supported. + args = sys.argv[1:] + decompress = args and args[0] == "-d" + if decompress: + args = args[1:] + if not args: + args = ["-"] + for arg in args: + if decompress: + if arg == "-": + f = GzipFile(filename="", mode="rb", fileobj=sys.stdin) + g = sys.stdout + else: + if arg[-3:] != ".gz": + print "filename doesn't end in .gz:", `arg` + continue + f = open(arg, "rb") + g = __builtin__.open(arg[:-3], "wb") + else: + if arg == "-": + f = sys.stdin + g = GzipFile(filename="", mode="wb", fileobj=sys.stdout) + else: + f = __builtin__.open(arg, "rb") + g = open(arg + ".gz", "wb") + while 1: + chunk = f.read(1024) + if not chunk: + break + g.write(chunk) + if g is not sys.stdout: + g.close() + if f is not sys.stdin: + f.close() + +if __name__ == '__main__': + _test() diff --git a/pyulib/src/ulib/ext/tarfile/tarfile.py b/pyulib/src/ulib/ext/tarfile/tarfile.py new file mode 100644 index 0000000..2a4934a --- /dev/null +++ b/pyulib/src/ulib/ext/tarfile/tarfile.py @@ -0,0 +1,2004 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +#------------------------------------------------------------------- +# tarfile.py +# +# Module for reading and writing .tar and tar.gz files. +# +# Needs at least Python version 2.1. +# +# Please consult the html documentation in this distribution +# for further details on how to use tarfile. +# +#------------------------------------------------------------------- +# Copyright (C) 2002 Lars Gustäbel +# All rights reserved. +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +"""Read from and write to tar format archives. +""" + +__version__ = "$Revision: 1.2 $" +# $Source: /var/cvsroot/tools/utools/pyutools/tarfile/tarfile.py,v $ + +version = "0.7.2" +__author__ = "Lars Gustäbel (lars@gustaebel.de)" +__date__ = "$Date: 2008/04/18 06:12:59 $" +__cvsid__ = "$Id: tarfile.py,v 1.2 2008/04/18 06:12:59 jclain Exp $" +__credits__ = "Gustavo Niemeyer, Niels Gustäbel, Richard Townsend" + +#--------- +# Imports +#--------- +import sys +import os +import shutil +import stat +import errno +import time +import struct +import fnmatch +import UserList + +import __builtin__ +file = __builtin__.open + +try: + import grp, pwd +except ImportError: + grp = pwd = None + +# We won't need this anymore in Python 2.3 +# +# We import the _tarfile extension, that contains +# some useful functions to handle devices and symlinks. +# We inject them into os module, as if we were under 2.3. +# +try: + import _tarfile + if _tarfile.mknod is None: + _tarfile = None +except ImportError: + _tarfile = None +if _tarfile and not hasattr(os, "mknod"): + os.mknod = _tarfile.mknod +if _tarfile and not hasattr(os, "major"): + os.major = _tarfile.major +if _tarfile and not hasattr(os, "minor"): + os.minor = _tarfile.minor +if _tarfile and not hasattr(os, "makedev"): + os.makedev = _tarfile.makedev +if _tarfile and not hasattr(os, "lchown"): + os.lchown = _tarfile.lchown + +# XXX remove for release (2.3) +try: + True + False +except NameError: + True = 1 + False = 0 + +# from tarfile import * +__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError"] + +#--------------------------------------------------------- +# tar constants +#--------------------------------------------------------- +NUL = "\0" # the null character +BLOCKSIZE = 512 # length of processing blocks +RECORDSIZE = BLOCKSIZE * 20 # length of records +MAGIC = "ustar" # magic tar string +VERSION = "00" # version number + +LENGTH_NAME = 100 # maximum length of a filename +LENGTH_LINK = 100 # maximum length of a linkname +LENGTH_PREFIX = 155 # maximum length of the prefix field +MAXSIZE_MEMBER = 077777777777L # maximum size of a file (11 octal digits) + +REGTYPE = "0" # regular file +AREGTYPE = "\0" # regular file +LNKTYPE = "1" # link (inside tarfile) +SYMTYPE = "2" # symbolic link +CHRTYPE = "3" # character special device +BLKTYPE = "4" # block special device +DIRTYPE = "5" # directory +FIFOTYPE = "6" # fifo special device +CONTTYPE = "7" # contiguous file + +GNUTYPE_LONGNAME = "L" # GNU tar extension for longnames +GNUTYPE_LONGLINK = "K" # GNU tar extension for longlink +GNUTYPE_SPARSE = "S" # GNU tar extension for sparse file + +#--------------------------------------------------------- +# tarfile constants +#--------------------------------------------------------- +SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE, # file types that tarfile + SYMTYPE, DIRTYPE, FIFOTYPE, # can cope with. + CONTTYPE, CHRTYPE, BLKTYPE, + GNUTYPE_LONGNAME, GNUTYPE_LONGLINK, + GNUTYPE_SPARSE) + +REGULAR_TYPES = (REGTYPE, AREGTYPE, # file types that somehow + CONTTYPE, GNUTYPE_SPARSE) # represent regular files + +#--------------------------------------------------------- +# Bits used in the mode field, values in octal. +#--------------------------------------------------------- +S_IFLNK = 0120000 # symbolic link +S_IFREG = 0100000 # regular file +S_IFBLK = 0060000 # block device +S_IFDIR = 0040000 # directory +S_IFCHR = 0020000 # character device +S_IFIFO = 0010000 # fifo + +TSUID = 04000 # set UID on execution +TSGID = 02000 # set GID on execution +TSVTX = 01000 # reserved + +TUREAD = 0400 # read by owner +TUWRITE = 0200 # write by owner +TUEXEC = 0100 # execute/search by owner +TGREAD = 0040 # read by group +TGWRITE = 0020 # write by group +TGEXEC = 0010 # execute/search by group +TOREAD = 0004 # read by other +TOWRITE = 0002 # write by other +TOEXEC = 0001 # execute/search by other + +#--------------------------------------------------------- +# Some useful functions +#--------------------------------------------------------- +def nts(s): + """Convert a null-terminated string buffer to a python string. + """ + return s.split(NUL, 1)[0] + +def calc_chksum(buf): + """Calculate the checksum for a member's header. It's a simple addition + of all bytes, treating the chksum field as if filled with spaces. + buf is a 512 byte long string buffer which holds the header. + """ + chk = 256 # chksum field is treated as blanks, + # so the initial value is 8 * ord(" ") + for c in buf[:148]: chk += ord(c) # sum up all bytes before chksum + for c in buf[156:]: chk += ord(c) # sum up all bytes after chksum + return chk + +def copyfileobj(src, dst, length=None): + """Copy length bytes from fileobj src to fileobj dst. + If length is None, copy the entire content. + """ + if length == 0: + return + if length is None: + shutil.copyfileobj(src, dst) + return + + BUFSIZE = 16 * 1024 + blocks, remainder = divmod(length, BUFSIZE) + for b in xrange(blocks): + buf = src.read(BUFSIZE) + if len(buf) < BUFSIZE: + raise IOError, "end of file reached" + dst.write(buf) + + if remainder != 0: + buf = src.read(remainder) + if len(buf) < remainder: + raise IOError, "end of file reached" + dst.write(buf) + return + +filemode_table = ( + (S_IFLNK, "l", + S_IFREG, "-", + S_IFBLK, "b", + S_IFDIR, "d", + S_IFCHR, "c", + S_IFIFO, "p"), + (TUREAD, "r"), + (TUWRITE, "w"), + (TUEXEC, "x", TSUID, "S", TUEXEC|TSUID, "s"), + (TGREAD, "r"), + (TGWRITE, "w"), + (TGEXEC, "x", TSGID, "S", TGEXEC|TSGID, "s"), + (TOREAD, "r"), + (TOWRITE, "w"), + (TOEXEC, "x", TSVTX, "T", TOEXEC|TSVTX, "t")) + +def filemode(mode): + """Convert a file's mode to a string of the form + -rwxrwxrwx. + Used by TarFile.list() + """ + s = "" + for t in filemode_table: + while True: + if mode & t[0] == t[0]: + s += t[1] + elif len(t) > 2: + t = t[2:] + continue + else: + s += "-" + break + return s + +if os.sep != "/": + normpath = lambda path: os.path.normpath(path).replace(os.sep, "/") +else: + normpath = os.path.normpath + +class TarError(Exception): + """Base exception.""" + pass +class ExtractError(TarError): + """General exception for extract errors.""" + pass +class ReadError(TarError): + """Exception for unreadble tar archives.""" + pass +class CompressionError(TarError): + """Exception for unavailable compression methods.""" + pass +class StreamError(TarError): + """Exception for unsupported operations on stream-like TarFiles.""" + pass + +#--------------------------- +# internal stream interface +#--------------------------- +class _LowLevelFile: + """Low-level file object. Supports reading and writing. + It is used instead of a regular file object for streaming + access. + """ + + def __init__(self, name, mode): + mode = { + "r": os.O_RDONLY, + "w": os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + }[mode] + mode |= getattr(os, "O_BINARY", 0) + self.fd = os.open(name, mode) + + def close(self): + os.close(self.fd) + + def read(self, size): + return os.read(self.fd, size) + + def write(self, s): + os.write(self.fd, s) + +class _Stream: + """Class that serves as an adapter between TarFile and + a stream-like object. The stream-like object only + needs to have a read() or write() method and is accessed + blockwise. Use of gzip or bzip2 compression is possible. + A stream-like object could be for example: sys.stdin, + sys.stdout, a socket, a tape device etc. + + _Stream is intended to be used only internally. + """ + + def __init__(self, name, mode, comptype, fileobj, bufsize): + """Construct a _Stream object. + """ + self._extfileobj = True + if fileobj is None: + fileobj = _LowLevelFile(name, mode) + self._extfileobj = False + + if comptype == '*': + # Enable transparent compression detection for the + # stream interface + fileobj = _StreamProxy(fileobj) + comptype = fileobj.getcomptype() + + self.name = name or "" + self.mode = mode + self.comptype = comptype + self.fileobj = fileobj + self.bufsize = bufsize + self.buf = "" + self.pos = 0L + self.closed = False + + if comptype == "gz": + try: + import zlib + except ImportError: + raise CompressionError, "zlib module is not available" + self.zlib = zlib + self.crc = zlib.crc32("") + if mode == "r": + self._init_read_gz() + else: + self._init_write_gz() + + if comptype == "bz2": + try: + import bz2 + except ImportError: + raise CompressionError, "bz2 module is not available" + if mode == "r": + self.dbuf = "" + self.cmp = bz2.BZ2Decompressor() + else: + self.cmp = bz2.BZ2Compressor() + + def __del__(self): + if hasattr(self, "closed") and not self.closed: + self.close() + + def _init_write_gz(self): + """Initialize for writing with gzip compression. + """ + self.cmp = self.zlib.compressobj(9, self.zlib.DEFLATED, + -self.zlib.MAX_WBITS, + self.zlib.DEF_MEM_LEVEL, + 0) + timestamp = struct.pack(" self.bufsize: + self.fileobj.write(self.buf[:self.bufsize]) + self.buf = self.buf[self.bufsize:] + + def close(self): + """Close the _Stream object. No operation should be + done on it afterwards. + """ + if self.closed: + return + + if self.mode == "w" and self.buf: + if self.comptype != "tar": + self.buf += self.cmp.flush() + blocks, remainder = divmod(len(self.buf), self.bufsize) + if remainder > 0: + self.buf += NUL * (self.bufsize - remainder) + self.fileobj.write(self.buf) + self.buf = "" + if self.comptype == "gz": + self.fileobj.write(struct.pack("= 0: + blocks, remainder = divmod(pos - self.pos, self.bufsize) + for i in xrange(blocks): + self.read(self.bufsize) + self.read(remainder) + else: + raise StreamError, "seeking backwards is not allowed" + return self.pos + + def read(self, size=None): + """Return the next size number of bytes from the stream. + If size is not defined, return all bytes of the stream + up to EOF. + """ + if size is None: + t = [] + while True: + buf = self._read(self.bufsize) + if not buf: + break + t.append(buf) + buf = "".join(t) + else: + buf = self._read(size) + self.pos += len(buf) + return buf + + def _read(self, size): + """Return size bytes from the stream. + """ + if self.comptype == "tar": + return self.__read(size) + + c = len(self.dbuf) + t = [self.dbuf] + while c < size: + buf = self.__read(self.bufsize) + if not buf: + break + buf = self.cmp.decompress(buf) + t.append(buf) + c += len(buf) + t = "".join(t) + self.dbuf = t[size:] + return t[:size] + + def __read(self, size): + """Return size bytes from stream. If internal buffer is empty, + read another block from the stream. + """ + c = len(self.buf) + t = [self.buf] + while c < size: + buf = self.fileobj.read(self.bufsize) + if not buf: + break + t.append(buf) + c += len(buf) + t = "".join(t) + self.buf = t[size:] + return t[:size] +# class _Stream + +class _StreamProxy: + """Small proxy class that enables transparent compression + detection for the Stream interface (mode 'r|*'). + """ + + def __init__(self, fileobj): + self.fileobj = fileobj + self.buf = self.fileobj.read(BLOCKSIZE) + + def read(self, size): + self.read = self.fileobj.read + return self.buf + + def getcomptype(self): + if self.buf.startswith("\037\213\010"): + return "gz" + if self.buf.startswith("BZh91"): + return "bz2" + return "tar" + + def close(self): + self.fileobj.close() +# class StreamProxy + +#------------------------ +# Extraction file object +#------------------------ +class ExFileObject: + """File-like object for reading an archive member. + Is returned by TarFile.extractfile(). Support for + sparse files included. + """ + + def __init__(self, tarfile, tarinfo): + self.fileobj = tarfile.fileobj + self.name = tarinfo.name + self.mode = "r" + self.closed = False + self.offset = tarinfo.offset_data + self.size = tarinfo.size + self.pos = 0L + self.linebuffer = "" + if tarinfo.issparse(): + self.sparse = tarinfo.sparse + self.read = self._readsparse + else: + self.read = self._readnormal + + def __read(self, size): + """Overloadable read method. + """ + return self.fileobj.read(size) + + def readline(self, size=-1): + """Read a line with approx. size. If size is negative, + read a whole line. readline() and read() must not + be mixed up (!). + """ + if size < 0: + size = sys.maxint + + nl = self.linebuffer.find("\n") + if nl >= 0: + nl = min(nl, size) + else: + size -= len(self.linebuffer) + while nl < 0: + buf = self.read(min(size, 100)) + if not buf: + break + self.linebuffer += buf + size -= len(buf) + if size <= 0: + break + nl = self.linebuffer.find("\n") + if nl == -1: + s = self.linebuffer + self.linebuffer = "" + return s + buf = self.linebuffer[:nl] + self.linebuffer = self.linebuffer[nl + 1:] + while buf[-1:] == "\r": + buf = buf[:-1] + return buf + "\n" + + def readlines(self): + """Return a list with all (following) lines. + """ + result = [] + while True: + line = self.readline() + if not line: break + result.append(line) + return result + + def _readnormal(self, size=None): + """Read operation for regular files. + """ + if self.closed: + raise ValueError, "file is closed" + self.fileobj.seek(self.offset + self.pos) + bytesleft = self.size - self.pos + if size is None: + bytestoread = bytesleft + else: + bytestoread = min(size, bytesleft) + self.pos += bytestoread + return self.__read(bytestoread) + + def _readsparse(self, size=None): + """Read operation for sparse files. + """ + if self.closed: + raise ValueError, "file is closed" + + if size is None: + size = self.size - self.pos + + data = [] + while size > 0: + buf = self._readsparsesection(size) + if not buf: + break + size -= len(buf) + data.append(buf) + return "".join(data) + + def _readsparsesection(self, size): + """Read a single section of a sparse file. + """ + section = self.sparse.find(self.pos) + + if section is None: + return "" + + toread = min(size, section.offset + section.size - self.pos) + if isinstance(section, _data): + realpos = section.realpos + self.pos - section.offset + self.pos += toread + self.fileobj.seek(self.offset + realpos) + return self.__read(toread) + else: + self.pos += toread + return NUL * toread + + def tell(self): + """Return the current file position. + """ + return self.pos + + def seek(self, pos, whence=0): + """Seek to a position in the file. + """ + self.linebuffer = "" + if whence == 0: + self.pos = min(max(pos, 0), self.size) + if whence == 1: + if pos < 0: + self.pos = max(self.pos + pos, 0) + else: + self.pos = min(self.pos + pos, self.size) + if whence == 2: + self.pos = max(min(self.size + pos, self.size), 0) + + def close(self): + """Close the file object. + """ + self.closed = True +#class ExFileObject + +#------------------ +# Exported Classes +#------------------ +class TarInfo: + """Informational class which holds the details about an + archive member given by a tar header block. + TarInfo objects are returned by TarFile.getmember(), + TarFile.getmembers() and TarFile.gettarinfo() and are + usually created internally. + """ + + def __init__(self, name=""): + """Construct a TarInfo object. name is the optional name + of the member. + """ + + self.name = name # member name (dirnames must end with '/') + self.mode = 0666 # file permissions + self.uid = 0 # user id + self.gid = 0 # group id + self.size = 0 # file size + self.mtime = 0 # modification time + self.chksum = 0 # header checksum + self.type = REGTYPE # member type + self.linkname = "" # link name + self.uname = "user" # user name + self.gname = "group" # group name + self.devmajor = 0 #- + self.devminor = 0 #-for use with CHRTYPE and BLKTYPE + self.prefix = "" # prefix to filename or holding information + # about sparse files + + self.offset = 0 # the tar header starts here + self.offset_data = 0 # the file's data starts here + + def __repr__(self): + return "<%s %r at %#x>" % (self.__class__.__name__,self.name,id(self)) + + def tobuf(self): + """Return a tar header block as a 512 byte string. + """ + # Prefer the size to be encoded as 11 octal ascii digits + # which is the most portable. If the size exceeds this + # limit (>= 8 GB), encode it as an 88-bit value which is + # a GNU tar feature. + if self.size <= MAXSIZE_MEMBER: + size = "%011o" % self.size + else: + s = self.size + size = "" + for i in range(11): + size = chr(s & 0377) + size + s >>= 8 + size = chr(0200) + size + + # The following code was contributed by Detlef Lannert. + parts = [] + for value, fieldsize in ( + (self.name, 100), + ("%07o" % (self.mode & 07777), 8), + ("%07o" % self.uid, 8), + ("%07o" % self.gid, 8), + (size, 12), + ("%011o" % self.mtime, 12), + (" ", 8), + (self.type, 1), + (self.linkname, 100), + (MAGIC, 6), + (VERSION, 2), + (self.uname, 32), + (self.gname, 32), + ("%07o" % self.devmajor, 8), + ("%07o" % self.devminor, 8), + (self.prefix, 155) + ): + l = len(value) + parts.append(value + (fieldsize - l) * NUL) + + buf = "".join(parts) + chksum = calc_chksum(buf) + buf = buf[:148] + "%06o\0" % chksum + buf[155:] + buf += (BLOCKSIZE - len(buf)) * NUL + self.buf = buf + return buf + + def isreg(self): + return self.type in REGULAR_TYPES + def isfile(self): + return self.isreg() + def isdir(self): + return self.type == DIRTYPE + def issym(self): + return self.type == SYMTYPE + def islnk(self): + return self.type == LNKTYPE + def ischr(self): + return self.type == CHRTYPE + def isblk(self): + return self.type == BLKTYPE + def isfifo(self): + return self.type == FIFOTYPE + def issparse(self): + return self.type == GNUTYPE_SPARSE + def isdev(self): + return self.type in (CHRTYPE, BLKTYPE, FIFOTYPE) +# class TarInfo + +def frombuf(buf): + """Construct a TarInfo object from a 512 byte string buffer. + """ + tarinfo = TarInfo() + tarinfo.name = nts(buf[0:100]) + tarinfo.mode = int(buf[100:108], 8) + tarinfo.uid = int(buf[108:116],8) + tarinfo.gid = int(buf[116:124],8) + tarinfo.size = long(buf[124:136], 8) + tarinfo.mtime = long(buf[136:148], 8) + tarinfo.chksum = int(buf[148:156], 8) + tarinfo.type = buf[156:157] + tarinfo.linkname = nts(buf[157:257]) + tarinfo.uname = nts(buf[265:297]) + tarinfo.gname = nts(buf[297:329]) + try: + tarinfo.devmajor = int(buf[329:337], 8) + tarinfo.devminor = int(buf[337:345], 8) + except ValueError: + tarinfo.devmajor = tarinfo.devmajor = 0 + + # The prefix field is used for filenames > 100 in + # the POSIX standard. + # name = prefix + "/" + name + prefix = buf[345:500] + while prefix and prefix[-1] == NUL: + prefix = prefix[:-1] + if len(prefix.split(NUL)) == 1: + tarinfo.prefix = prefix + tarinfo.name = normpath(os.path.join(tarinfo.prefix, tarinfo.name)) + else: + tarinfo.prefix = buf[345:500] + + # Directory names should have a '/' at the end. + if tarinfo.isdir() and tarinfo.name[-1:] != "/": + tarinfo.name += "/" + return tarinfo + +#-------------------------------------------------------------------------- +# Below are the classmethods which act as alternate constructors to the +# TarFile class. The open() method is the only one that is needed for +# public use; it is the "super"-constructor and is able to select an +# adequate "sub"-constructor for a particular compression using the mapping +# from OPEN_METH. +# +# This concept allows one to subclass TarFile without losing the comfort of +# the super-constructor. A sub-constructor is registered and made available +# by adding it to the mapping in OPEN_METH. + +def open(name=None, mode="r", fileobj=None, bufsize=20*512): + """Open a tar archive for reading, writing or appending. Return + an appropriate TarFile class. + + mode: + 'r' or 'r:*' open for reading with transparent compression + 'r:' open for reading exclusively uncompressed + 'r:gz' open for reading with gzip compression + 'r:bz2' open for reading with bzip2 compression + 'a' or 'a:' open for appending + 'w' or 'w:' open for writing without compression + 'w:gz' open for writing with gzip compression + 'w:bz2' open for writing with bzip2 compression + + 'r|*' open a stream of tar blocks with transparent compression + 'r|' open an uncompressed stream of tar blocks for reading + 'r|gz' open a gzip compressed stream of tar blocks + 'r|bz2' open a bzip2 compressed stream of tar blocks + 'w|' open an uncompressed stream for writing + 'w|gz' open a gzip compressed stream for writing + 'w|bz2' open a bzip2 compressed stream for writing + """ + + if not name and not fileobj: + raise ValueError, "nothing to open" + + if mode in ("r", "r:*"): + # Find out which *open() is appropriate for opening the file. + for comptype in OPEN_METH.keys(): + func = OPEN_METH[comptype] + try: + return func(name, "r", fileobj) + except (ReadError, CompressionError): + continue + raise ReadError, "file could not be opened successfully" + + elif ":" in mode: + filemode, comptype = mode.split(":", 1) + filemode = filemode or "r" + comptype = comptype or "tar" + + # Select the *open() function according to + # given compression. + if comptype in OPEN_METH.keys(): + func = OPEN_METH[comptype] + else: + raise CompressionError, "unknown compression type %r" % comptype + return func(name, filemode, fileobj) + + elif "|" in mode: + filemode, comptype = mode.split("|", 1) + filemode = filemode or "r" + comptype = comptype or "tar" + + if filemode not in "rw": + raise ValueError, "mode must be 'r' or 'w'" + + t = TarFile(name, filemode, + _Stream(name, filemode, comptype, fileobj, bufsize)) + t._extfileobj = False + return t + + elif mode in "aw": + return taropen(name, mode, fileobj) + + raise ValueError, "undiscernible mode" + +def taropen(name, mode="r", fileobj=None): + """Open uncompressed tar archive name for reading or writing. + """ + if len(mode) > 1 or mode not in "raw": + raise ValueError, "mode must be 'r', 'a' or 'w'" + return TarFile(name, mode, fileobj) + +def gzopen(name, mode="r", fileobj=None, compresslevel=9): + """Open gzip compressed tar archive name for reading or writing. + Appending is not allowed. + """ + if len(mode) > 1 or mode not in "rw": + raise ValueError, "mode must be 'r' or 'w'" + + try: + import gzip22 as gzip + except ImportError: + raise CompressionError, "gzip module is not available" + + pre, ext = os.path.splitext(name) + pre = os.path.basename(pre) + if ext == ".tgz": + ext = ".tar" + if ext == ".gz": + ext = "" + tarname = pre + ext + + if fileobj is None: + fileobj = file(name, mode + "b") + + if mode != "r": + name = tarname + + try: + t = taropen(tarname, mode, + gzip.GzipFile(name, mode, compresslevel, fileobj) + ) + except IOError: + raise ReadError, "not a gzip file" + t._extfileobj = False + return t + +def bz2open(name, mode="r", fileobj=None, compresslevel=9): + """Open bzip2 compressed tar archive name for reading or writing. + Appending is not allowed. + """ + if len(mode) > 1 or mode not in "rw": + raise ValueError, "mode must be 'r' or 'w'." + + try: + import bz2 + except ImportError: + raise CompressionError, "bz2 module is not available" + + pre, ext = os.path.splitext(name) + pre = os.path.basename(pre) + if ext == ".tbz2": + ext = ".tar" + if ext == ".bz2": + ext = "" + tarname = pre + ext + + if fileobj is not None: + raise ValueError, "no support for external file objects" + + try: + t = taropen(tarname, mode, bz2.BZ2File(name, mode, compresslevel=compresslevel)) + except IOError: + raise ReadError, "not a bzip2 file" + t._extfileobj = False + return t + +# All *open() methods are registered here. +OPEN_METH = { + "tar": taropen, # uncompressed tar + "gz": gzopen, # gzip compressed tar + "bz2": bz2open # bzip2 compressed tar +} + +class TarFile: + """The TarFile Class provides an interface to tar archives. + """ + + debug = 0 # May be set from 0 (no msgs) to 3 (all msgs) + + dereference = False # If true, add content of linked file to the + # tar file, else the link. + + ignore_zeros = False # If true, skips empty or invalid blocks and + # continues processing. + + errorlevel = 0 # If 0, fatal errors only appear in debug + # messages (if debug >= 0). If > 0, errors + # are passed to the caller as exceptions. + + posix = True # If True, generates POSIX.1-1990-compliant + # archives (no GNU extensions!) + + fileobject = ExFileObject + + def __init__(self, name=None, mode="r", fileobj=None): + """Open an (uncompressed) tar archive `name'. `mode' is either 'r' to + read from an existing archive, 'a' to append data to an existing + file or 'w' to create a new file overwriting an existing one. `mode' + defaults to 'r'. + If `fileobj' is given, it is used for reading or writing data. If it + can be determined, `mode' is overridden by `fileobj's mode. + `fileobj' is not closed, when TarFile is closed. + """ + self.name = name + + if len(mode) > 1 or mode not in "raw": + raise ValueError, "mode must be 'r', 'a' or 'w'" + self._mode = mode + self.mode = {"r": "rb", "a": "r+b", "w": "wb"}[mode] + + if not fileobj: + fileobj = file(self.name, self.mode) + self._extfileobj = False + else: + if self.name is None and hasattr(fileobj, "name"): + self.name = fileobj.name + if hasattr(fileobj, "mode"): + self.mode = fileobj.mode + self._extfileobj = True + self.fileobj = fileobj + + # Init datastructures + self.closed = False + self.members = [] # list of members as TarInfo objects + self.membernames = [] # names of members + self.chunks = [0] # chunk cache + self._loaded = False # flag if all members have been read + self.offset = 0L # current position in the archive file + self.inodes = {} # dictionary caching the inodes of + # archive members already added + + if self._mode == "r": + self.firstmember = None + self.firstmember = self.next() + + if self._mode == "a": + # Move to the end of the archive, + # before the first empty block. + self.firstmember = None + while True: + try: + tarinfo = self.next() + except ReadError: + self.fileobj.seek(0) + break + if tarinfo is None: + self.fileobj.seek(- BLOCKSIZE, 1) + break + + if self._mode in "aw": + self._loaded = True + + #-------------------------------------------------------------------------- + # The public methods which TarFile provides: + + def close(self): + """Close the TarFile. In write-mode, two finishing zero blocks are + appended to the archive. + """ + if self.closed: + return + + if self._mode in "aw": + self.fileobj.write(NUL * (BLOCKSIZE * 2)) + self.offset += (BLOCKSIZE * 2) + # fill up the end with zero-blocks + # (like option -b20 for tar does) + blocks, remainder = divmod(self.offset, RECORDSIZE) + if remainder > 0: + self.fileobj.write(NUL * (RECORDSIZE - remainder)) + self.offset += RECORDSIZE - remainder + + if not self._extfileobj: + self.fileobj.close() + self.closed = True + + def getmember(self, name): + """Return a TarInfo object for member `name'. If `name' can not be + found in the archive, KeyError is raised. If a member occurs more + than once in the archive, its last occurence is assumed to be the + most up-to-date version. + """ + self._check() + if name not in self.membernames and not self._loaded: + self._load() + if name not in self.membernames: + raise KeyError, "filename %r not found" % name + return self._getmember(name) + + def getmembers(self): + """Return the members of the archive as a list of TarInfo objects. The + list has the same order as the members in the archive. + """ + self._check() + if not self._loaded: # if we want to obtain a list of + self._load() # all members, we first have to + # scan the whole archive. + return self.members + + def getnames(self): + """Return the members of the archive as a list of their names. It has + the same order as the list returned by getmembers(). + """ + self._check() + if not self._loaded: + self._load() + return self.membernames + + def gettarinfo(self, name=None, arcname=None, fileobj=None): + """Create a TarInfo object for either the file `name' or the file + object `fileobj' (using os.fstat on its file descriptor). You can + modify some of the TarInfo's attributes before you add it using + addfile(). If given, `arcname' specifies an alternative name for the + file in the archive. + """ + self._check("aw") + + # When fileobj is given, replace name by + # fileobj's real name. + if fileobj is not None: + name = fileobj.name + + # Building the name of the member in the archive. + # Backward slashes are converted to forward slashes, + # Absolute paths are turned to relative paths. + if arcname is None: + arcname = name + arcname = normpath(arcname) + drv, arcname = os.path.splitdrive(arcname) + while arcname[0:1] == "/": + arcname = arcname[1:] + + # Now, fill the TarInfo object with + # information specific for the file. + tarinfo = TarInfo() + + # Use os.stat or os.lstat, depending on platform + # and if symlinks shall be resolved. + if fileobj is None: + if hasattr(os, "lstat") and not self.dereference: + statres = os.lstat(name) + else: + statres = os.stat(name) + else: + statres = os.fstat(fileobj.fileno()) + linkname = "" + + stmd = statres[stat.ST_MODE] + + if stat.S_ISREG(stmd): + inode = (statres[stat.ST_INO], statres[stat.ST_DEV]) + if self.inodes.has_key(inode) and not self.dereference: + # Is it a hardlink to an already + # archived file? + type = LNKTYPE + linkname = self.inodes[inode] + else: + # The inode is added only if its valid. + # For win32 it is always 0. + type = REGTYPE + if inode[0]: + self.inodes[inode] = arcname + elif stat.S_ISDIR(stmd): + type = DIRTYPE + if arcname[-1:] != "/": + arcname += "/" + elif stat.S_ISFIFO(stmd): + type = FIFOTYPE + elif stat.S_ISLNK(stmd): + type = SYMTYPE + linkname = os.readlink(name) + elif stat.S_ISCHR(stmd): + type = CHRTYPE + elif stat.S_ISBLK(stmd): + type = BLKTYPE + else: + return None + + # Fill the TarInfo object with all + # information we can get. + tarinfo.name = arcname + tarinfo.mode = stmd + tarinfo.uid = statres[stat.ST_UID] + tarinfo.gid = statres[stat.ST_GID] + tarinfo.size = statres[stat.ST_SIZE] + tarinfo.mtime = statres[stat.ST_MTIME] + tarinfo.type = type + tarinfo.linkname = linkname + if pwd: + try: + tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0] + except KeyError: + pass + if grp: + try: + tarinfo.gname = grp.getgrgid(tarinfo.gid)[0] + except KeyError: + pass + + if type in (CHRTYPE, BLKTYPE): + if hasattr(os, "major") and hasattr(os, "minor"): + tarinfo.devmajor = os.major(statres[stat.ST_DEV]) + tarinfo.devminor = os.minor(statres[stat.ST_DEV]) + return tarinfo + + def list(self, verbose=True): + """Print a table of contents to sys.stdout. If `verbose' is False, only + the names of the members are printed. If it is True, an `ls -l'-like + output is produced. + """ + self._check() + + for tarinfo in self.getmembers(): + if verbose: + print filemode(tarinfo.mode), + print "%s/%s" % (tarinfo.uname or tarinfo.uid, + tarinfo.gname or tarinfo.gid), + if tarinfo.ischr() or tarinfo.isblk(): + print "%10s" % ("%d,%d" \ + % (tarinfo.devmajor, tarinfo.devminor)), + else: + print "%10d" % tarinfo.size, + print "%d-%02d-%02d %02d:%02d:%02d" \ + % time.localtime(tarinfo.mtime)[:6], + + print tarinfo.name, + + if verbose: + if tarinfo.issym(): + print "->", tarinfo.linkname, + if tarinfo.islnk(): + print "link to", tarinfo.linkname, + print + + def add(self, name, arcname=None, recursive=True, exclude=None): + """Add the file `name' to the archive. `name' may be any type of file + (directory, fifo, symbolic link, etc.). If given, `arcname' + specifies an alternative name for the file in the archive. + Directories are added recursively by default. This can be avoided by + setting `recursive' to False. `exclude' is a sequence of shell + wildcard patterns that will not be added to the archive. + """ + self._check("aw") + + if arcname is None: + arcname = name + + # Exclude pathnames that match the wildcard patterns. + if exclude is not None: + for pattern in exclude: + if fnmatch.fnmatch(name, pattern): + self._dbg(2, "tarfile: Excluded %r" % name) + return + + # Skip if somebody tries to archive the archive... + if self.name is not None \ + and os.path.abspath(name) == os.path.abspath(self.name): + self._dbg(2, "tarfile: Skipped %r" % name) + return + + # Special case: The user wants to add the current + # working directory. + if name == ".": + if recursive: + if arcname == ".": + arcname = "" + for f in os.listdir("."): + self.add(f, os.path.join(arcname, f), exclude=exclude) + return + + self._dbg(1, name) + + # Create a TarInfo object from the file. + tarinfo = self.gettarinfo(name, arcname) + + if tarinfo is None: + self._dbg(1, "tarfile: Unsupported type %r" % name) + return + + # Append the tar header and data to the archive. + if tarinfo.isreg(): + f = file(name, "rb") + self.addfile(tarinfo, f) + f.close() + + if tarinfo.type in (LNKTYPE, SYMTYPE, FIFOTYPE, CHRTYPE, BLKTYPE): + tarinfo.size = 0L + self.addfile(tarinfo) + + if tarinfo.isdir(): + self.addfile(tarinfo) + if recursive: + for f in os.listdir(name): + self.add(os.path.join(name, f), os.path.join(arcname, f), exclude=exclude) + + def addfile(self, tarinfo, fileobj=None): + """Add the TarInfo object `tarinfo' to the archive. If `fileobj' is + given, tarinfo.size bytes are read from it and added to the archive. + You can create TarInfo objects using gettarinfo(). + On Windows platforms, `fileobj' should always be opened with mode + 'rb' to avoid irritation about the file size. + """ + self._check("aw") + + tarinfo.name = normpath(tarinfo.name) + if tarinfo.isdir(): + tarinfo.name += "/" + + if tarinfo.linkname: + tarinfo.linkname = normpath(tarinfo.linkname) + + if tarinfo.size > MAXSIZE_MEMBER: + if self.posix: + raise ValueError, "file is too large (>= 8 GB)" + else: + self._dbg(2, "tarfile: Created GNU tar largefile header") + + if len(tarinfo.linkname) > LENGTH_LINK: + if self.posix: + raise ValueError, "linkname is too long (>%d)" \ + % (LENGTH_LINK) + else: + self._create_gnulong(tarinfo.linkname, GNUTYPE_LONGLINK) + tarinfo.linkname = tarinfo.linkname[:LENGTH_LINK -1] + self._dbg(2, "tarfile: Created GNU tar extension LONGLINK") + + if len(tarinfo.name) > LENGTH_NAME: + if self.posix: + prefix = tarinfo.name[:LENGTH_PREFIX + 1] + while prefix and prefix[-1] != "/": + prefix = prefix[:-1] + + name = tarinfo.name[len(prefix):] + prefix = prefix[:-1] + + if not prefix or len(name) > LENGTH_NAME: + raise ValueError, "name is too long (>%d)" \ + % (LENGTH_NAME) + + tarinfo.name = name + tarinfo.prefix = prefix + else: + self._create_gnulong(tarinfo.name, GNUTYPE_LONGNAME) + tarinfo.name = tarinfo.name[:LENGTH_NAME - 1] + self._dbg(2, "tarfile: Created GNU tar extension LONGNAME") + + self.fileobj.write(tarinfo.tobuf()) + self.offset += BLOCKSIZE + + # If there's data to follow, append it. + if fileobj is not None: + copyfileobj(fileobj, self.fileobj, tarinfo.size) + blocks, remainder = divmod(tarinfo.size, BLOCKSIZE) + if remainder > 0: + self.fileobj.write(NUL * (BLOCKSIZE - remainder)) + blocks += 1 + self.offset += blocks * BLOCKSIZE + + self.members.append(tarinfo) + self.membernames.append(tarinfo.name) + self.chunks.append(self.offset) + + def extract(self, member, path=""): + """Extract a member from the archive to the current working directory, + using its full name. Its file information is extracted as accurately + as possible. `member' may be a filename or a TarInfo object. You can + specify a different directory using `path'. + """ + self._check("r") + + if isinstance(member, TarInfo): + tarinfo = member + else: + tarinfo = self.getmember(member) + + # Here we prepare the link targets for makelink(). + # This is a quick fix for SF bug #857297. + if tarinfo.islnk(): + tarinfo._link_target = os.path.join(path, tarinfo.linkname) + + try: + self._extract_member(tarinfo, os.path.join(path, tarinfo.name)) + except EnvironmentError, e: + if self.errorlevel > 0: + raise + else: + if e.filename is None: + self._dbg(1, "tarfile: %s" % e.strerror) + else: + self._dbg(1, "tarfile: %s %r" % (e.strerror, e.filename)) + except ExtractError, e: + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + def extractfile(self, member): + """Extract a member from the archive as a file object. `member' may be + a filename or a TarInfo object. If `member' is a regular file, a + file-like object is returned. If `member' is a link, a file-like + object is constructed from the link's target. If `member' is none of + the above, None is returned. + The file-like object is read-only and provides the following + methods: read(), readline(), readlines(), seek() and tell() + """ + self._check("r") + + if isinstance(member, TarInfo): + tarinfo = member + else: + tarinfo = self.getmember(member) + + if tarinfo.isreg(): + return self.fileobject(self, tarinfo) + + elif tarinfo.type not in SUPPORTED_TYPES: + # If a member's type is unknown, it is treated as a + # regular file. + return self.fileobject(self, tarinfo) + + elif tarinfo.islnk() or tarinfo.issym(): + if isinstance(self.fileobj, _Stream): + # A small but ugly workaround for the case that someone tries + # to extract a (sym)link as a file-object from a non-seekable + # stream of tar blocks. + raise StreamError, "cannot extract (sym)link as file object" + else: + # A (sym)link's file object is it's target's file object. + return self.extractfile(self._getmember(tarinfo.linkname, + tarinfo)) + else: + # If there's no data associated with the member (directory, chrdev, + # blkdev, etc.), return None instead of a file object. + return None + + def _extract_member(self, tarinfo, targetpath): + """Extract the TarInfo object tarinfo to a physical + file called targetpath. + """ + # Fetch the TarInfo object for the given name + # and build the destination pathname, replacing + # forward slashes to platform specific separators. + if targetpath[-1:] == "/": + targetpath = targetpath[:-1] + targetpath = os.path.normpath(targetpath) + + # Create all upper directories. + upperdirs = os.path.dirname(targetpath) + if upperdirs and not os.path.exists(upperdirs): + ti = TarInfo() + ti.name = upperdirs + ti.type = DIRTYPE + ti.mode = 0777 + ti.mtime = tarinfo.mtime + ti.uid = tarinfo.uid + ti.gid = tarinfo.gid + ti.uname = tarinfo.uname + ti.gname = tarinfo.gname + try: + self._extract_member(ti, ti.name) + except: + pass + + if tarinfo.islnk() or tarinfo.issym(): + self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname)) + else: + self._dbg(1, tarinfo.name) + + if tarinfo.isreg(): + self.makefile(tarinfo, targetpath) + elif tarinfo.isdir(): + self.makedir(tarinfo, targetpath) + elif tarinfo.isfifo(): + self.makefifo(tarinfo, targetpath) + elif tarinfo.ischr() or tarinfo.isblk(): + self.makedev(tarinfo, targetpath) + elif tarinfo.islnk() or tarinfo.issym(): + self.makelink(tarinfo, targetpath) + elif tarinfo.type not in SUPPORTED_TYPES: + self.makeunknown(tarinfo, targetpath) + else: + self.makefile(tarinfo, targetpath) + + self.chown(tarinfo, targetpath) + if not tarinfo.issym(): + self.chmod(tarinfo, targetpath) + self.utime(tarinfo, targetpath) + + #-------------------------------------------------------------------------- + # Below are the different file methods. They are called via + # _extract_member() when extract() is called. They can be replaced in a + # subclass to implement other functionality. + + def makedir(self, tarinfo, targetpath): + """Make a directory called targetpath. + """ + try: + os.mkdir(targetpath) + except EnvironmentError, e: + if e.errno != errno.EEXIST: + raise + + def makefile(self, tarinfo, targetpath): + """Make a file called targetpath. + """ + source = self.extractfile(tarinfo) + target = file(targetpath, "wb") + copyfileobj(source, target) + source.close() + target.close() + + def makeunknown(self, tarinfo, targetpath): + """Make a file from a TarInfo object with an unknown type + at targetpath. + """ + self.makefile(tarinfo, targetpath) + self._dbg(1, "tarfile: Unknown file type %r, " \ + "extracted as regular file." % tarinfo.type) + + def makefifo(self, tarinfo, targetpath): + """Make a fifo called targetpath. + """ + if hasattr(os, "mkfifo"): + os.mkfifo(targetpath) + else: + raise ExtractError, "fifo not supported by system" + + def makedev(self, tarinfo, targetpath): + """Make a character or block device called targetpath. + """ + if not hasattr(os, "mknod") or not hasattr(os, "makedev"): + raise ExtractError, "special devices not supported by system" + + mode = tarinfo.mode + if tarinfo.isblk(): + mode |= stat.S_IFBLK + else: + mode |= stat.S_IFCHR + + # XXX This if statement should go away when + # python-2.3a0-devicemacros patch succeeds. + if hasattr(os, "makedev"): + os.mknod(targetpath, mode, + os.makedev(tarinfo.devmajor, tarinfo.devminor)) + else: + os.mknod(targetpath, mode, + tarinfo.devmajor, tarinfo.devminor) + + def makelink(self, tarinfo, targetpath): + """Make a (symbolic) link called targetpath. If it cannot be created + (platform limitation), we try to make a copy of the referenced file + instead of a link. + """ + linkpath = tarinfo.linkname + try: + if tarinfo.issym(): + os.symlink(linkpath, targetpath) + else: + # See extract(). + os.link(tarinfo._link_target, targetpath) + except AttributeError: + if tarinfo.issym(): + linkpath = os.path.join(os.path.dirname(tarinfo.name), + linkpath) + linkpath = normpath(linkpath) + + try: + self._extract_member(self.getmember(linkpath), targetpath) + except (EnvironmentError, KeyError), e: + linkpath = os.path.normpath(linkpath) + try: + shutil.copy2(linkpath, targetpath) + except EnvironmentError, e: + raise IOError, "link could not be created" + + def chown(self, tarinfo, targetpath): + """Set owner of targetpath according to tarinfo. + """ + if pwd and hasattr(os, "geteuid") and os.geteuid() == 0: + # We have to be root to do so. + try: + g = grp.getgrnam(tarinfo.gname)[2] + except KeyError: + try: + g = grp.getgrgid(tarinfo.gid)[2] + except KeyError: + g = os.getgid() + try: + u = pwd.getpwnam(tarinfo.uname)[2] + except KeyError: + try: + u = pwd.getpwuid(tarinfo.uid)[2] + except KeyError: + u = os.getuid() + try: + if tarinfo.issym() and hasattr(os, "lchown"): + os.lchown(targetpath, u, g) + else: + os.chown(targetpath, u, g) + except EnvironmentError, e: + raise ExtractError, "could not change owner" + + def chmod(self, tarinfo, targetpath): + """Set file permissions of targetpath according to tarinfo. + """ + try: + os.chmod(targetpath, tarinfo.mode) + except EnvironmentError, e: + raise ExtractError, "could not change mode" + + def utime(self, tarinfo, targetpath): + """Set modification time of targetpath according to tarinfo. + """ + if sys.platform == "win32" and tarinfo.isdir(): + # According to msdn.microsoft.com, it is an error (EACCES) + # to use utime() on directories. + return + try: + os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime)) + except EnvironmentError, e: + raise ExtractError, "could not change modification time" + + #-------------------------------------------------------------------------- + + def next(self): + """Return the next member of the archive as a TarInfo object, when + TarFile is opened for reading. Return None if there is no more + available. + """ + self._check("ra") + if self.firstmember is not None: + m = self.firstmember + self.firstmember = None + return m + + # Read the next block. + self.fileobj.seek(self.chunks[-1]) + while True: + buf = self.fileobj.read(BLOCKSIZE) + if not buf: + self._loaded = True + return None + try: + tarinfo = frombuf(buf) + except ValueError: + if self.ignore_zeros: + if buf.count(NUL) == BLOCKSIZE: + adj = "empty" + else: + adj = "invalid" + self._dbg(2, "0x%X: %s block" % (self.offset, adj)) + self.offset += BLOCKSIZE + continue + else: + # Block is empty or unreadable. + if self.chunks[-1] == 0: + # If the first block is invalid. That does not + # look like a tar archive we can handle. + raise ReadError,"empty, unreadable or compressed file" + self._loaded = True + return None + break + + # We shouldn't rely on this checksum, because some tar programs + # calculate it differently and it is merely validating the + # header block. We could just as well skip this part, which would + # have a slight effect on performance... + if tarinfo.chksum != calc_chksum(buf): + self._dbg(1, "tarfile: Bad Checksum %r" % tarinfo.name) + + # Set the TarInfo object's offset to the current position of the + # TarFile and set self.offset to the position where the data blocks + # should begin. + tarinfo.offset = self.offset + self.offset += BLOCKSIZE + + # Check if the TarInfo object has a typeflag for which a callback + # method is registered in the TYPE_METH. If so, then call it. + if tarinfo.type in self.TYPE_METH.keys(): + tarinfo = self.TYPE_METH[tarinfo.type](self, tarinfo) + else: + tarinfo.offset_data = self.offset + if tarinfo.isreg() or tarinfo.type not in SUPPORTED_TYPES: + # Skip the following data blocks. + self.offset += self._block(tarinfo.size) + + if tarinfo.isreg() and tarinfo.name[:-1] == "/": + # some old tar programs don't know DIRTYPE + tarinfo.type = DIRTYPE + + self.members.append(tarinfo) + self.membernames.append(tarinfo.name) + self.chunks.append(self.offset) + return tarinfo + + #-------------------------------------------------------------------------- + # Below are some methods which are called for special typeflags in the + # next() method, e.g. for unwrapping GNU longname/longlink blocks. They + # are registered in TYPE_METH below. You can register your own methods + # with this mapping. + # A registered method is called with a TarInfo object as only argument. + # + # During its execution the method MUST perform the following tasks: + # 1. set tarinfo.offset_data to the position where the data blocks begin, + # if there is data to follow. + # 2. set self.offset to the position where the next member's header will + # begin. + # 3. return a valid TarInfo object. + + def proc_gnulong(self, tarinfo): + """Evaluate the blocks that hold a GNU longname + or longlink member. + """ + buf = "" + name = None + linkname = None + count = tarinfo.size + while count > 0: + block = self.fileobj.read(BLOCKSIZE) + buf += block + self.offset += BLOCKSIZE + count -= BLOCKSIZE + + if tarinfo.type == GNUTYPE_LONGNAME: + name = nts(buf) + if tarinfo.type == GNUTYPE_LONGLINK: + linkname = nts(buf) + + buf = self.fileobj.read(BLOCKSIZE) + + tarinfo = frombuf(buf) + tarinfo.offset = self.offset + self.offset += BLOCKSIZE + tarinfo.offset_data = self.offset + tarinfo.name = name or tarinfo.name + tarinfo.linkname = linkname or tarinfo.linkname + + if tarinfo.isreg() or tarinfo.type not in SUPPORTED_TYPES: + # Skip the following data blocks. + self.offset += self._block(tarinfo.size) + + return tarinfo + + def proc_sparse(self, tarinfo): + """Analyze a GNU sparse header plus extra headers. + """ + buf = tarinfo.tobuf() + sp = _ringbuffer() + pos = 386 + lastpos = 0L + realpos = 0L + # There are 4 possible sparse structs in the + # first header. + for i in xrange(4): + try: + offset = int(buf[pos:pos + 12], 8) + numbytes = int(buf[pos + 12:pos + 24], 8) + except ValueError: + break + if offset > lastpos: + sp.append(_hole(lastpos, offset - lastpos)) + sp.append(_data(offset, numbytes, realpos)) + realpos += numbytes + lastpos = offset + numbytes + pos += 24 + + isextended = ord(buf[482]) + origsize = int(buf[483:495], 8) + + # If the isextended flag is given, + # there are extra headers to process. + while isextended == 1: + buf = self.fileobj.read(BLOCKSIZE) + self.offset += BLOCKSIZE + pos = 0 + for i in xrange(21): + try: + offset = int(buf[pos:pos + 12], 8) + numbytes = int(buf[pos + 12:pos + 24], 8) + except ValueError: + break + if offset > lastpos: + sp.append(_hole(lastpos, offset - lastpos)) + sp.append(_data(offset, numbytes, realpos)) + realpos += numbytes + lastpos = offset + numbytes + pos += 24 + isextended = ord(buf[504]) + + if lastpos < origsize: + sp.append(_hole(lastpos, origsize - lastpos)) + + tarinfo.sparse = sp + + tarinfo.offset_data = self.offset + self.offset += self._block(tarinfo.size) + tarinfo.size = origsize + return tarinfo + + # The type mapping for the next() method. The keys are single character + # strings, the typeflag. The values are methods which are called when + # next() encounters such a typeflag. + TYPE_METH = { + GNUTYPE_LONGNAME: proc_gnulong, + GNUTYPE_LONGLINK: proc_gnulong, + GNUTYPE_SPARSE: proc_sparse + } + + #-------------------------------------------------------------------------- + # Little helper methods: + + def _block(self, count): + """Round up a byte count by BLOCKSIZE and return it, + e.g. _block(834) => 1024. + """ + blocks, remainder = divmod(count, BLOCKSIZE) + if remainder: + blocks += 1 + return blocks * BLOCKSIZE + + def _getmember(self, name, tarinfo=None): + """Find an archive member by name from bottom to top. + If tarinfo is given, it is used as the starting point. + """ + if tarinfo is None: + end = len(self.members) + else: + end = self.members.index(tarinfo) + + for i in xrange(end - 1, -1, -1): + if name == self.membernames[i]: + return self.members[i] + + def _load(self): + """Read through the entire archive file and look for readable + members. + """ + while True: + tarinfo = self.next() + if tarinfo is None: + break + self._loaded = True + + def _check(self, mode=None): + """Check if TarFile is still open, and if the operation's mode + corresponds to TarFile's mode. + """ + if self.closed: + raise IOError, "%s is closed" % self.__class__.__name__ + if mode is not None and self._mode not in mode: + raise IOError, "bad operation for mode %r" % self._mode + + def _create_gnulong(self, name, type): + """Write a GNU longname/longlink member to the TarFile. + It consists of an extended tar header, with the length + of the longname as size, followed by data blocks, + which contain the longname as a null terminated string. + """ + name += NUL + + tarinfo = TarInfo() + tarinfo.name = "././@LongLink" + tarinfo.type = type + tarinfo.mode = 0 + tarinfo.size = len(name) + + # write extended header + self.fileobj.write(tarinfo.tobuf()) + self.offset += BLOCKSIZE + # write name blocks + self.fileobj.write(name) + blocks, remainder = divmod(tarinfo.size, BLOCKSIZE) + if remainder > 0: + self.fileobj.write(NUL * (BLOCKSIZE - remainder)) + blocks += 1 + self.offset += blocks * BLOCKSIZE + + def _dbg(self, level, msg): + """Write debugging output to sys.stderr. + """ + if level <= self.debug: + print >> sys.stderr, msg +# class TarFile + +# Helper classes for sparse file support +class _section: + """Base class for _data and _hole. + """ + def __init__(self, offset, size): + self.offset = offset + self.size = size + def __contains__(self, offset): + return self.offset <= offset < self.offset + self.size + +class _data(_section): + """Represent a data section in a sparse file. + """ + def __init__(self, offset, size, realpos): + _section.__init__(self, offset, size) + self.realpos = realpos + +class _hole(_section): + """Represent a hole section in a sparse file. + """ + pass + +class _ringbuffer(UserList.UserList): + """Ringbuffer class which increases performance + over a regular list. + """ + def __init__(self): + UserList.UserList.__init__(self) + self.idx = 0 + def find(self, offset): + idx = self.idx + while True: + item = self.data[idx] + if offset in item: + break + idx += 1 + if idx == len(self.data): + idx = 0 + if idx == self.idx: + # End of File + return None + self.idx = idx + return item + +#--------------------------------------------- +# zipfile compatible TarFile class +#--------------------------------------------- +TAR_PLAIN = 0 # zipfile.ZIP_STORED +TAR_GZIPPED = 8 # zipfile.ZIP_DEFLATED +class TarFileCompat: + """TarFile class compatible with standard module zipfile's + ZipFile class. + """ + def __init__(self, file, mode="r", compression=TAR_PLAIN): + if compression == TAR_PLAIN: + self.tarfile = taropen(file, mode) + elif compression == TAR_GZIPPED: + self.tarfile = gzopen(file, mode) + else: + raise ValueError, "unknown compression constant" + if mode[0:1] == "r": + members = self.tarfile.getmembers() + for i in xrange(len(members)): + m = members[i] + m.filename = m.name + m.file_size = m.size + m.date_time = time.gmtime(m.mtime)[:6] + def namelist(self): + return map(lambda m: m.name, self.infolist()) + def infolist(self): + return filter(lambda m: m.type in REGULAR_TYPES, + self.tarfile.getmembers()) + def printdir(self): + self.tarfile.list() + def testzip(self): + return + def getinfo(self, name): + return self.tarfile.getmember(name) + def read(self, name): + return self.tarfile.extractfile(self.tarfile.getmember(name)).read() + def write(self, filename, arcname=None, compress_type=None): + self.tarfile.add(filename, arcname) + def writestr(self, zinfo, bytes): + import StringIO + import calendar + zinfo.name = zinfo.filename + zinfo.size = zinfo.file_size + zinfo.mtime = calendar.timegm(zinfo.date_time) + self.tarfile.addfile(zinfo, StringIO.StringIO(bytes)) + def close(self): + self.tarfile.close() +#class TarFileCompat + +#-------------------- +# exported functions +#-------------------- +def is_tarfile(name): + """Return True if name points to a tar archive that we + are able to handle, else return False. + """ + try: + t = open(name) + t.close() + return True + except TarError: + return False + diff --git a/pyulib/src/ulib/ext/web/__init__.py b/pyulib/src/ulib/ext/web/__init__.py new file mode 100644 index 0000000..cdf077b --- /dev/null +++ b/pyulib/src/ulib/ext/web/__init__.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +"""web.py: makes web apps (http://webpy.org)""" + +from __future__ import generators + +__version__ = "0.33" +__author__ = [ + "Aaron Swartz ", + "Anand Chitipothu " +] +__license__ = "public domain" +__contributors__ = "see http://webpy.org/changes" + +import utils, db, net, wsgi, http, webapi, httpserver, debugerror +import template, form + +import session + +from utils import * +from db import * +from net import * +from wsgi import * +from http import * +from webapi import * +from httpserver import * +from debugerror import * +from application import * +from browser import * +#import test +try: + import webopenid as openid +except ImportError: + pass # requires openid module + diff --git a/pyulib/src/ulib/ext/web/__init__.pyc b/pyulib/src/ulib/ext/web/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6bcb7e7a105755ed40b7c9df653b3991ae11a03 GIT binary patch literal 1030 zcmZXS+iuh_5QZn&b`RM@x0I6vNV#Ln#RjQ&RaF&nkhnpJR)BmHoY>8}o7j=WXO0KfnU+5lJx>j26SNC*Mz z0qFtT1GWzZh7kHd_JIw6^nLaa*2sgFhd_ockARF?j(|ihkAaL^o&cG&JOwgsc?M+G z@&S;8mghj`Egu3o^gMDJuAFwHVMn8mqK?KLO*)!(H0$V2M@MkA?MmJSat}VqBiQ5j z;U)q#fB=`oQGI4ZxLibTp2ui5JX_Y=)3~B*ZesLeTGu9CtgNk1lLQ0xcByo6EPmr! z;1>Yv4i{Wt6ojj-U+2R3O zJ3y1BOyz0nRj287V;jxW^aJjI9sB*STpOWemqQ!7QX1XeHY(uK;2Asj>A8|ti>z_Y zV{jjXjTNP_Ari}M4*lgCh4A1s+rEqNjMl=daWTU+miI2>SyOPWm9|4nw7jZI zYPmmtt#swPG2A$JG^6;E#R5(l1aRGn0cH6=HR#C}z2l6u>eB1??U6A%2Gb$>~cra}svrua8``DeR> j=WZxwiKZXU!+zKcMqxkbhhKt+;UtWL|1Qj!2eaT0$;$0I literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/application.py b/pyulib/src/ulib/ext/web/application.py new file mode 100755 index 0000000..2b40d55 --- /dev/null +++ b/pyulib/src/ulib/ext/web/application.py @@ -0,0 +1,678 @@ +#!/usr/bin/python +""" +Web application +(from web.py) +""" +import webapi as web +import webapi, wsgi, utils +import debugerror +from utils import lstrips, safeunicode +import sys + +import urllib +import traceback +import itertools +import os +import re +import types +from exceptions import SystemExit + +try: + import wsgiref.handlers +except ImportError: + pass # don't break people with old Pythons + +__all__ = [ + "application", "auto_application", + "subdir_application", "subdomain_application", + "loadhook", "unloadhook", + "autodelegate" +] + +class application: + """ + Application to delegate requests based on path. + + >>> urls = ("/hello", "hello") + >>> app = application(urls, globals()) + >>> class hello: + ... def GET(self): return "hello" + >>> + >>> app.request("/hello").data + 'hello' + """ + def __init__(self, mapping=(), fvars={}, autoreload=None): + if autoreload is None: + autoreload = web.config.get('debug', False) + self.mapping = mapping + self.fvars = fvars + self.processors = [] + + self.add_processor(loadhook(self._load)) + self.add_processor(unloadhook(self._unload)) + + if autoreload: + def main_module_name(): + mod = sys.modules['__main__'] + file = getattr(mod, '__file__', None) # make sure this works even from python interpreter + return file and os.path.splitext(os.path.basename(file))[0] + + def modname(fvars): + """find name of the module name from fvars.""" + file, name = fvars.get('__file__'), fvars.get('__name__') + if file is None or name is None: + return None + + if name == '__main__': + # Since the __main__ module can't be reloaded, the module has + # to be imported using its file name. + name = main_module_name() + return name + + mapping_name = utils.dictfind(fvars, mapping) + module_name = modname(fvars) + + def reload_mapping(): + """loadhook to reload mapping and fvars.""" + mod = __import__(module_name) + mapping = getattr(mod, mapping_name, None) + if mapping: + self.fvars = mod.__dict__ + self.mapping = mapping + + self.add_processor(loadhook(Reloader())) + if mapping_name and module_name: + self.add_processor(loadhook(reload_mapping)) + + # load __main__ module usings its filename, so that it can be reloaded. + if main_module_name() and '__main__' in sys.argv: + try: + __import__(main_module_name()) + except ImportError: + pass + + def _load(self): + web.ctx.app_stack.append(self) + + def _unload(self): + web.ctx.app_stack = web.ctx.app_stack[:-1] + + if web.ctx.app_stack: + # this is a sub-application, revert ctx to earlier state. + oldctx = web.ctx.get('_oldctx') + if oldctx: + web.ctx.home = oldctx.home + web.ctx.homepath = oldctx.homepath + web.ctx.path = oldctx.path + web.ctx.fullpath = oldctx.fullpath + + def _cleanup(self): + #@@@ + # Since the CherryPy Webserver uses thread pool, the thread-local state is never cleared. + # This interferes with the other requests. + # clearing the thread-local storage to avoid that. + # see utils.ThreadedDict for details + import threading + t = threading.currentThread() + if hasattr(t, '_d'): + del t._d + + def add_mapping(self, pattern, classname): + self.mapping += (pattern, classname) + + def add_processor(self, processor): + """ + Adds a processor to the application. + + >>> urls = ("/(.*)", "echo") + >>> app = application(urls, globals()) + >>> class echo: + ... def GET(self, name): return name + ... + >>> + >>> def hello(handler): return "hello, " + handler() + ... + >>> app.add_processor(hello) + >>> app.request("/web.py").data + 'hello, web.py' + """ + self.processors.append(processor) + + def request(self, localpart='/', method='GET', data=None, + host="0.0.0.0:8080", headers=None, https=False, **kw): + """Makes request to this application for the specified path and method. + Response will be a storage object with data, status and headers. + + >>> urls = ("/hello", "hello") + >>> app = application(urls, globals()) + >>> class hello: + ... def GET(self): + ... web.header('Content-Type', 'text/plain') + ... return "hello" + ... + >>> response = app.request("/hello") + >>> response.data + 'hello' + >>> response.status + '200 OK' + >>> response.headers['Content-Type'] + 'text/plain' + + To use https, use https=True. + + >>> urls = ("/redirect", "redirect") + >>> app = application(urls, globals()) + >>> class redirect: + ... def GET(self): raise web.seeother("/foo") + ... + >>> response = app.request("/redirect") + >>> response.headers['Location'] + 'http://0.0.0.0:8080/foo' + >>> response = app.request("/redirect", https=True) + >>> response.headers['Location'] + 'https://0.0.0.0:8080/foo' + + The headers argument specifies HTTP headers as a mapping object + such as a dict. + + >>> urls = ('/ua', 'uaprinter') + >>> class uaprinter: + ... def GET(self): + ... return 'your user-agent is ' + web.ctx.env['HTTP_USER_AGENT'] + ... + >>> app = application(urls, globals()) + >>> app.request('/ua', headers = { + ... 'User-Agent': 'a small jumping bean/1.0 (compatible)' + ... }).data + 'your user-agent is a small jumping bean/1.0 (compatible)' + + """ + path, maybe_query = urllib.splitquery(localpart) + query = maybe_query or "" + + if 'env' in kw: + env = kw['env'] + else: + env = {} + env = dict(env, HTTP_HOST=host, REQUEST_METHOD=method, PATH_INFO=path, QUERY_STRING=query, HTTPS=str(https)) + headers = headers or {} + + for k, v in headers.items(): + env['HTTP_' + k.upper().replace('-', '_')] = v + + if 'HTTP_CONTENT_LENGTH' in env: + env['CONTENT_LENGTH'] = env.pop('HTTP_CONTENT_LENGTH') + + if 'HTTP_CONTENT_TYPE' in env: + env['CONTENT_TYPE'] = env.pop('HTTP_CONTENT_TYPE') + + if method in ["POST", "PUT"]: + data = data or '' + import StringIO + if isinstance(data, dict): + q = urllib.urlencode(data) + else: + q = data + env['wsgi.input'] = StringIO.StringIO(q) + if not env.get('CONTENT_TYPE', '').lower().startswith('multipart/') and 'CONTENT_LENGTH' not in env: + env['CONTENT_LENGTH'] = len(q) + response = web.storage() + def start_response(status, headers): + response.status = status + response.headers = dict(headers) + response.header_items = headers + response.data = "".join(self.wsgifunc()(env, start_response)) + return response + + def browser(self): + import browser + return browser.AppBrowser(self) + + def handle(self): + fn, args = self._match(self.mapping, web.ctx.path) + return self._delegate(fn, self.fvars, args) + + def handle_with_processors(self): + def process(processors): + try: + if processors: + p, processors = processors[0], processors[1:] + return p(lambda: process(processors)) + else: + return self.handle() + except web.HTTPError: + raise + except (KeyboardInterrupt, SystemExit): + raise + except: + print >> web.debug, traceback.format_exc() + raise self.internalerror() + + # processors must be applied in the resvere order. (??) + return process(self.processors) + + def wsgifunc(self, *middleware): + """Returns a WSGI-compatible function for this application.""" + def peep(iterator): + """Peeps into an iterator by doing an iteration + and returns an equivalent iterator. + """ + # wsgi requires the headers first + # so we need to do an iteration + # and save the result for later + try: + firstchunk = iterator.next() + except StopIteration: + firstchunk = '' + + return itertools.chain([firstchunk], iterator) + + def is_generator(x): return x and hasattr(x, 'next') + + def wsgi(env, start_resp): + self.load(env) + try: + # allow uppercase methods only + if web.ctx.method.upper() != web.ctx.method: + raise web.nomethod() + + result = self.handle_with_processors() + if is_generator(result): + result = peep(result) + else: + result = [result] + except web.HTTPError, e: + result = [e.data] + + result = web.utf8(iter(result)) + + status, headers = web.ctx.status, web.ctx.headers + start_resp(status, headers) + + def cleanup(): + self._cleanup() + yield '' # force this function to be a generator + + return itertools.chain(result, cleanup()) + + for m in middleware: + wsgi = m(wsgi) + + return wsgi + + def run(self, *middleware): + """ + Starts handling requests. If called in a CGI or FastCGI context, it will follow + that protocol. If called from the command line, it will start an HTTP + server on the port named in the first command line argument, or, if there + is no argument, on port 8080. + + `middleware` is a list of WSGI middleware which is applied to the resulting WSGI + function. + """ + return wsgi.runwsgi(self.wsgifunc(*middleware)) + + def cgirun(self, *middleware): + """ + Return a CGI handler. This is mostly useful with Google App Engine. + There you can just do: + + main = app.cgirun() + """ + wsgiapp = self.wsgifunc(*middleware) + + try: + from google.appengine.ext.webapp.util import run_wsgi_app + return run_wsgi_app(wsgiapp) + except ImportError: + # we're not running from within Google App Engine + return wsgiref.handlers.CGIHandler().run(wsgiapp) + + def load(self, env): + """Initializes ctx using env.""" + ctx = web.ctx + ctx.clear() + ctx.status = '200 OK' + ctx.headers = [] + ctx.output = '' + ctx.environ = ctx.env = env + ctx.host = env.get('HTTP_HOST') + + if env.get('wsgi.url_scheme') in ['http', 'https']: + ctx.protocol = env['wsgi.url_scheme'] + elif env.get('HTTPS', '').lower() in ['on', 'true', '1']: + ctx.protocol = 'https' + else: + ctx.protocol = 'http' + ctx.homedomain = ctx.protocol + '://' + env.get('HTTP_HOST', '[unknown]') + ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', '')) + ctx.home = ctx.homedomain + ctx.homepath + #@@ home is changed when the request is handled to a sub-application. + #@@ but the real home is required for doing absolute redirects. + ctx.realhome = ctx.home + ctx.ip = env.get('REMOTE_ADDR') + ctx.method = env.get('REQUEST_METHOD') + ctx.path = env.get('PATH_INFO') or '' + # http://trac.lighttpd.net/trac/ticket/406 requires: + if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'): + ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], ctx.homepath) + # Apache and CherryPy webservers unquote the url but lighttpd doesn't. + # unquote explicitly for lighttpd to make ctx.path uniform across all servers. + ctx.path = urllib.unquote(ctx.path) + + if env.get('QUERY_STRING'): + ctx.query = '?' + env.get('QUERY_STRING', '') + else: + ctx.query = '' + + ctx.fullpath = ctx.path + ctx.query + + for k, v in ctx.iteritems(): + if isinstance(v, str): + ctx[k] = safeunicode(v) + + # status must always be str + ctx.status = '200 OK' + + ctx.app_stack = [] + + _handler_configurator = None + + def set_handler_configurator(self, handler_configurator): + self._handler_configurator = handler_configurator + + def configure_handler(self, handler): + if self._handler_configurator is not None: + self._handler_configurator(handler) + + def _delegate(self, f, fvars, args=[]): + def handle_class(cls): + meth = web.ctx.method + if meth == 'HEAD' and not hasattr(cls, meth): + meth = 'GET' + if not hasattr(cls, meth): + raise web.nomethod(cls) + handler = cls() + self.configure_handler(handler) + tocall = getattr(handler, meth) + return tocall(*args) + + def is_class(o): return isinstance(o, (types.ClassType, type)) + + if f is None: + raise web.notfound() + elif isinstance(f, application): + return f.handle_with_processors() + elif is_class(f): + return handle_class(f) + elif isinstance(f, basestring): + if f.startswith('redirect '): + url = f.split(' ', 1)[1] + if web.ctx.method == "GET": + x = web.ctx.env.get('QUERY_STRING', '') + if x: + url += '?' + x + raise web.redirect(url) + elif '.' in f: + x = f.split('.') + mod, cls = '.'.join(x[:-1]), x[-1] + mod = __import__(mod, globals(), locals(), [""]) + cls = getattr(mod, cls) + else: + cls = fvars[f] + return handle_class(cls) + elif hasattr(f, '__call__'): + return f() + else: + return web.notfound() + + def _match(self, mapping, value): + for pat, what in utils.group(mapping, 2): + if isinstance(what, application): + if value.startswith(pat): + f = lambda: self._delegate_sub_application(pat, what) + return f, None + else: + continue + elif isinstance(what, basestring): + what, result = utils.re_subm('^' + pat + '$', what, value) + else: + result = utils.re_compile('^' + pat + '$').match(value) + + if result: # it's a match + return what, [x for x in result.groups()] + return None, None + + def _delegate_sub_application(self, dir, app): + """Deletes request to sub application `app` rooted at the directory `dir`. + The home, homepath, path and fullpath values in web.ctx are updated to mimic request + to the subapp and are restored after it is handled. + + @@Any issues with when used with yield? + """ + web.ctx._oldctx = web.storage(web.ctx) + web.ctx.home += dir + web.ctx.homepath += dir + web.ctx.path = web.ctx.path[len(dir):] + web.ctx.fullpath = web.ctx.fullpath[len(dir):] + return app.handle_with_processors() + + def get_parent_app(self): + if self in web.ctx.app_stack: + index = web.ctx.app_stack.index(self) + if index > 0: + return web.ctx.app_stack[index-1] + + def notfound(self): + """Returns HTTPError with '404 not found' message""" + parent = self.get_parent_app() + if parent: + return parent.notfound() + else: + return web._NotFound() + + def internalerror(self): + """Returns HTTPError with '500 internal error' message""" + parent = self.get_parent_app() + if parent: + return parent.internalerror() + elif web.config.get('debug'): + import debugerror + return debugerror.debugerror() + else: + return web._InternalError() + +class auto_application(application): + """Application similar to `application` but urls are constructed + automatiacally using metaclass. + + >>> app = auto_application() + >>> class hello(app.page): + ... def GET(self): return "hello, world" + ... + >>> class foo(app.page): + ... path = '/foo/.*' + ... def GET(self): return "foo" + >>> app.request("/hello").data + 'hello, world' + >>> app.request('/foo/bar').data + 'foo' + """ + def __init__(self): + application.__init__(self) + + class metapage(type): + def __init__(klass, name, bases, attrs): + type.__init__(klass, name, bases, attrs) + path = attrs.get('path', '/' + name) + + # path can be specified as None to ignore that class + # typically required to create a abstract base class. + if path is not None: + self.add_mapping(path, klass) + + class page: + path = None + __metaclass__ = metapage + + self.page = page + +# The application class already has the required functionality of subdir_application +subdir_application = application + +class subdomain_application(application): + """ + Application to delegate requests based on the host. + + >>> urls = ("/hello", "hello") + >>> app = application(urls, globals()) + >>> class hello: + ... def GET(self): return "hello" + >>> + >>> mapping = (r"hello\.example\.com", app) + >>> app2 = subdomain_application(mapping) + >>> app2.request("/hello", host="hello.example.com").data + 'hello' + >>> response = app2.request("/hello", host="something.example.com") + >>> response.status + '404 Not Found' + >>> response.data + 'not found' + """ + def handle(self): + host = web.ctx.host.split(':')[0] #strip port + fn, args = self._match(self.mapping, host) + return self._delegate(fn, self.fvars, args) + + def _match(self, mapping, value): + for pat, what in utils.group(mapping, 2): + if isinstance(what, basestring): + what, result = utils.re_subm('^' + pat + '$', what, value) + else: + result = utils.re_compile('^' + pat + '$').match(value) + + if result: # it's a match + return what, [x for x in result.groups()] + return None, None + +def loadhook(h): + """ + Converts a load hook into an application processor. + + >>> app = auto_application() + >>> def f(): "something done before handling request" + ... + >>> app.add_processor(loadhook(f)) + """ + def processor(handler): + h() + return handler() + + return processor + +def unloadhook(h): + """ + Converts an unload hook into an application processor. + + >>> app = auto_application() + >>> def f(): "something done after handling request" + ... + >>> app.add_processor(unloadhook(f)) + """ + def processor(handler): + try: + result = handler() + is_generator = result and hasattr(result, 'next') + except: + # run the hook even when handler raises some exception + h() + raise + + if is_generator: + return wrap(result) + else: + h() + return result + + def wrap(result): + def next(): + try: + return result.next() + except: + # call the hook at the and of iterator + h() + raise + + result = iter(result) + while True: + yield next() + + return processor + +def autodelegate(prefix=''): + """ + Returns a method that takes one argument and calls the method named prefix+arg, + calling `notfound()` if there isn't one. Example: + + urls = ('/prefs/(.*)', 'prefs') + + class prefs: + GET = autodelegate('GET_') + def GET_password(self): pass + def GET_privacy(self): pass + + `GET_password` would get called for `/prefs/password` while `GET_privacy` for + `GET_privacy` gets called for `/prefs/privacy`. + + If a user visits `/prefs/password/change` then `GET_password(self, '/change')` + is called. + """ + def internal(self, arg): + if '/' in arg: + first, rest = arg.split('/', 1) + func = prefix + first + args = ['/' + rest] + else: + func = prefix + arg + args = [] + + if hasattr(self, func): + try: + return getattr(self, func)(*args) + except TypeError: + raise web.notfound() + else: + raise web.notfound() + return internal + +class Reloader: + """Checks to see if any loaded modules have changed on disk and, + if so, reloads them. + """ + def __init__(self): + self.mtimes = {} + + def __call__(self): + for mod in sys.modules.values(): + self.check(mod) + + def check(self, mod): + try: + mtime = os.stat(mod.__file__).st_mtime + except (AttributeError, OSError, IOError): + return + if mod.__file__.endswith('.pyc') and os.path.exists(mod.__file__[:-1]): + mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime) + + if mod not in self.mtimes: + self.mtimes[mod] = mtime + elif self.mtimes[mod] < mtime: + try: + reload(mod) + self.mtimes[mod] = mtime + except ImportError: + pass + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/pyulib/src/ulib/ext/web/application.pyc b/pyulib/src/ulib/ext/web/application.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f62c43e0736a41b7117c7ecec0d67e049a602d0 GIT binary patch literal 26204 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 CG|Zd; literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/contrib/__init__.py b/pyulib/src/ulib/ext/web/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyulib/src/ulib/ext/web/contrib/template.py b/pyulib/src/ulib/ext/web/contrib/template.py new file mode 100644 index 0000000..7495d39 --- /dev/null +++ b/pyulib/src/ulib/ext/web/contrib/template.py @@ -0,0 +1,131 @@ +""" +Interface to various templating engines. +""" +import os.path + +__all__ = [ + "render_cheetah", "render_genshi", "render_mako", + "cache", +] + +class render_cheetah: + """Rendering interface to Cheetah Templates. + + Example: + + render = render_cheetah('templates') + render.hello(name="cheetah") + """ + def __init__(self, path): + # give error if Chetah is not installed + from Cheetah.Template import Template + self.path = path + + def __getattr__(self, name): + from Cheetah.Template import Template + path = os.path.join(self.path, name + ".html") + + def template(**kw): + t = Template(file=path, searchList=[kw]) + return t.respond() + + return template + +class render_genshi: + """Rendering interface genshi templates. + Example: + + for xml/html templates. + + render = render_genshi(['templates/']) + render.hello(name='genshi') + + For text templates: + + render = render_genshi(['templates/'], type='text') + render.hello(name='genshi') + """ + + def __init__(self, *a, **kwargs): + from genshi.template import TemplateLoader + + self._type = kwargs.pop('type', None) + self._loader = TemplateLoader(*a, **kwargs) + + def __getattr__(self, name): + # Assuming all templates are html + path = name + ".html" + + if self._type == "text": + from genshi.template import TextTemplate + cls = TextTemplate + type = "text" + else: + cls = None + type = None + + t = self._loader.load(path, cls=cls) + def template(**kw): + stream = t.generate(**kw) + if type: + return stream.render(type) + else: + return stream.render() + return template + +class render_jinja: + """Rendering interface to Jinja2 Templates + + Example: + + render= render_jinja('templates') + render.hello(name='jinja2') + """ + def __init__(self, *a, **kwargs): + extensions = kwargs.pop('extensions', []) + globals = kwargs.pop('globals', {}) + + from jinja2 import Environment,FileSystemLoader + self._lookup = Environment(loader=FileSystemLoader(*a, **kwargs), extensions=extensions) + self._lookup.globals.update(globals) + + def __getattr__(self, name): + # Assuming all templates end with .html + path = name + '.html' + t = self._lookup.get_template(path) + return t.render + +class render_mako: + """Rendering interface to Mako Templates. + + Example: + + render = render_mako(directories=['templates']) + render.hello(name="mako") + """ + def __init__(self, *a, **kwargs): + from mako.lookup import TemplateLookup + self._lookup = TemplateLookup(*a, **kwargs) + + def __getattr__(self, name): + # Assuming all templates are html + path = name + ".html" + t = self._lookup.get_template(path) + return t.render + +class cache: + """Cache for any rendering interface. + + Example: + + render = cache(render_cheetah("templates/")) + render.hello(name='cache') + """ + def __init__(self, render): + self._render = render + self._cache = {} + + def __getattr__(self, name): + if name not in self._cache: + self._cache[name] = getattr(self._render, name) + return self._cache[name] diff --git a/pyulib/src/ulib/ext/web/db.py b/pyulib/src/ulib/ext/web/db.py new file mode 100644 index 0000000..8ba1e2f --- /dev/null +++ b/pyulib/src/ulib/ext/web/db.py @@ -0,0 +1,1168 @@ +""" +Database API +(part of web.py) +""" + +__all__ = [ + "UnknownParamstyle", "UnknownDB", "TransactionError", + "sqllist", "sqlors", "reparam", "sqlquote", + "SQLQuery", "SQLParam", "sqlparam", + "SQLLiteral", "sqlliteral", + "database", 'DB', +] + +import time +try: + import datetime +except ImportError: + datetime = None + +try: set +except NameError: + from sets import Set as set + +from utils import threadeddict, storage, iters, iterbetter, safestr, safeunicode + +try: + # db module can work independent of web.py + from webapi import debug, config +except: + import sys + debug = sys.stderr + config = storage() + +class UnknownDB(Exception): + """raised for unsupported dbms""" + pass + +class _ItplError(ValueError): + def __init__(self, text, pos): + ValueError.__init__(self) + self.text = text + self.pos = pos + def __str__(self): + return "unfinished expression in %s at char %d" % ( + repr(self.text), self.pos) + +class TransactionError(Exception): pass + +class UnknownParamstyle(Exception): + """ + raised for unsupported db paramstyles + + (currently supported: qmark, numeric, format, pyformat) + """ + pass + +class SQLParam: + """ + Parameter in SQLQuery. + + >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam("joe")]) + >>> q + + >>> q.query() + 'SELECT * FROM test WHERE name=%s' + >>> q.values() + ['joe'] + """ + def __init__(self, value): + self.value = value + + def get_marker(self, paramstyle='pyformat'): + if paramstyle == 'qmark': + return '?' + elif paramstyle == 'numeric': + return ':1' + elif paramstyle is None or paramstyle in ['format', 'pyformat']: + return '%s' + raise UnknownParamstyle, paramstyle + + def sqlquery(self): + return SQLQuery([self]) + + def __add__(self, other): + return self.sqlquery() + other + + def __radd__(self, other): + return other + self.sqlquery() + + def __str__(self): + return str(self.value) + + def __repr__(self): + return '' % repr(self.value) + +sqlparam = SQLParam + +class SQLQuery: + """ + You can pass this sort of thing as a clause in any db function. + Otherwise, you can pass a dictionary to the keyword argument `vars` + and the function will call reparam for you. + + Internally, consists of `items`, which is a list of strings and + SQLParams, which get concatenated to produce the actual query. + """ + # tested in sqlquote's docstring + def __init__(self, items=[]): + r"""Creates a new SQLQuery. + + >>> SQLQuery("x") + + >>> q = SQLQuery(['SELECT * FROM ', 'test', ' WHERE x=', SQLParam(1)]) + >>> q + + >>> q.query(), q.values() + ('SELECT * FROM test WHERE x=%s', [1]) + >>> SQLQuery(SQLParam(1)) + + """ + if isinstance(items, list): + self.items = items + elif isinstance(items, SQLParam): + self.items = [items] + elif isinstance(items, SQLQuery): + self.items = list(items.items) + else: + self.items = [items] + + # Take care of SQLLiterals + for i, item in enumerate(self.items): + if isinstance(item, SQLParam) and isinstance(item.value, SQLLiteral): + self.items[i] = item.value.v + + def __add__(self, other): + if isinstance(other, basestring): + items = [other] + elif isinstance(other, SQLQuery): + items = other.items + else: + return NotImplemented + return SQLQuery(self.items + items) + + def __radd__(self, other): + if isinstance(other, basestring): + items = [other] + else: + return NotImplemented + + return SQLQuery(items + self.items) + + def __iadd__(self, other): + if isinstance(other, basestring): + items = [other] + elif isinstance(other, SQLQuery): + items = other.items + else: + return NotImplemented + self.items.extend(items) + return self + + def __len__(self): + return len(self.query()) + + def query(self, paramstyle=None): + """ + Returns the query part of the sql query. + >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam('joe')]) + >>> q.query() + 'SELECT * FROM test WHERE name=%s' + >>> q.query(paramstyle='qmark') + 'SELECT * FROM test WHERE name=?' + """ + s = '' + for x in self.items: + if isinstance(x, SQLParam): + x = x.get_marker(paramstyle) + s += safestr(x) + return s + + def values(self): + """ + Returns the values of the parameters used in the sql query. + >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam('joe')]) + >>> q.values() + ['joe'] + """ + return [i.value for i in self.items if isinstance(i, SQLParam)] + + def join(items, sep=' '): + """ + Joins multiple queries. + + >>> SQLQuery.join(['a', 'b'], ', ') + + """ + if len(items) == 0: + return SQLQuery("") + + q = SQLQuery(items[0]) + for item in items[1:]: + q += sep + q += item + return q + + join = staticmethod(join) + + def _str(self): + try: + return self.query() % tuple([sqlify(x) for x in self.values()]) + except (ValueError, TypeError): + return self.query() + + def __str__(self): + return safestr(self._str()) + + def __unicode__(self): + return safeunicode(self._str()) + + def __repr__(self): + return '' % repr(str(self)) + +class SQLLiteral: + """ + Protects a string from `sqlquote`. + + >>> sqlquote('NOW()') + + >>> sqlquote(SQLLiteral('NOW()')) + + """ + def __init__(self, v): + self.v = v + + def __repr__(self): + return self.v + +sqlliteral = SQLLiteral + +def _sqllist(values): + """ + >>> _sqllist([1, 2, 3]) + + """ + items = [] + items.append('(') + for i, v in enumerate(values): + if i != 0: + items.append(', ') + items.append(sqlparam(v)) + items.append(')') + return SQLQuery(items) + +def reparam(string_, dictionary): + """ + Takes a string and a dictionary and interpolates the string + using values from the dictionary. Returns an `SQLQuery` for the result. + + >>> reparam("s = $s", dict(s=True)) + + >>> reparam("s IN $s", dict(s=[1, 2])) + + """ + dictionary = dictionary.copy() # eval mucks with it + vals = [] + result = [] + for live, chunk in _interpolate(string_): + if live: + v = eval(chunk, dictionary) + result.append(sqlquote(v)) + else: + result.append(chunk) + return SQLQuery.join(result, '') + +def sqlify(obj): + """ + converts `obj` to its proper SQL version + + >>> sqlify(None) + 'NULL' + >>> sqlify(True) + "'t'" + >>> sqlify(3) + '3' + """ + # because `1 == True and hash(1) == hash(True)` + # we have to do this the hard way... + + if obj is None: + return 'NULL' + elif obj is True: + return "'t'" + elif obj is False: + return "'f'" + elif datetime and isinstance(obj, datetime.datetime): + return repr(obj.isoformat()) + else: + if isinstance(obj, unicode): obj = obj.encode('utf8') + return repr(obj) + +def sqllist(lst): + """ + Converts the arguments for use in something like a WHERE clause. + + >>> sqllist(['a', 'b']) + 'a, b' + >>> sqllist('a') + 'a' + >>> sqllist(u'abc') + u'abc' + """ + if isinstance(lst, basestring): + return lst + else: + return ', '.join(lst) + +def sqlors(left, lst): + """ + `left is a SQL clause like `tablename.arg = ` + and `lst` is a list of values. Returns a reparam-style + pair featuring the SQL that ORs together the clause + for each item in the lst. + + >>> sqlors('foo = ', []) + + >>> sqlors('foo = ', [1]) + + >>> sqlors('foo = ', 1) + + >>> sqlors('foo = ', [1,2,3]) + + """ + if isinstance(lst, iters): + lst = list(lst) + ln = len(lst) + if ln == 0: + return SQLQuery("1=2") + if ln == 1: + lst = lst[0] + + if isinstance(lst, iters): + return SQLQuery(['('] + + sum([[left, sqlparam(x), ' OR '] for x in lst], []) + + ['1=2)'] + ) + else: + return left + sqlparam(lst) + +def sqlwhere(dictionary, grouping=' AND '): + """ + Converts a `dictionary` to an SQL WHERE clause `SQLQuery`. + + >>> sqlwhere({'cust_id': 2, 'order_id':3}) + + >>> sqlwhere({'cust_id': 2, 'order_id':3}, grouping=', ') + + >>> sqlwhere({'a': 'a', 'b': 'b'}).query() + 'a = %s AND b = %s' + """ + return SQLQuery.join([k + ' = ' + sqlparam(v) for k, v in dictionary.items()], grouping) + +def sqlquote(a): + """ + Ensures `a` is quoted properly for use in a SQL query. + + >>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3) + + >>> 'WHERE x = ' + sqlquote(True) + ' AND y IN ' + sqlquote([2, 3]) + + """ + if isinstance(a, list): + return _sqllist(a) + else: + return sqlparam(a).sqlquery() + +class Transaction: + """Database transaction.""" + def __init__(self, ctx): + self.ctx = ctx + self.transaction_count = transaction_count = len(ctx.transactions) + + class transaction_engine: + """Transaction Engine used in top level transactions.""" + def do_transact(self): + ctx.commit(unload=False) + + def do_commit(self): + ctx.commit() + + def do_rollback(self): + ctx.rollback() + + class subtransaction_engine: + """Transaction Engine used in sub transactions.""" + def query(self, q): + db_cursor = ctx.db.cursor() + ctx.db_execute(db_cursor, SQLQuery(q % transaction_count)) + + def do_transact(self): + self.query('SAVEPOINT webpy_sp_%s') + + def do_commit(self): + self.query('RELEASE SAVEPOINT webpy_sp_%s') + + def do_rollback(self): + self.query('ROLLBACK TO SAVEPOINT webpy_sp_%s') + + class dummy_engine: + """Transaction Engine used instead of subtransaction_engine + when sub transactions are not supported.""" + do_transact = do_commit = do_rollback = lambda self: None + + if self.transaction_count: + # nested transactions are not supported in some databases + if self.ctx.get('ignore_nested_transactions'): + self.engine = dummy_engine() + else: + self.engine = subtransaction_engine() + else: + self.engine = transaction_engine() + + self.engine.do_transact() + self.ctx.transactions.append(self) + + def __enter__(self): + return self + + def __exit__(self, exctype, excvalue, traceback): + if exctype is not None: + self.rollback() + else: + self.commit() + + def commit(self): + if len(self.ctx.transactions) > self.transaction_count: + self.engine.do_commit() + self.ctx.transactions = self.ctx.transactions[:self.transaction_count] + + def rollback(self): + if len(self.ctx.transactions) > self.transaction_count: + self.engine.do_rollback() + self.ctx.transactions = self.ctx.transactions[:self.transaction_count] + +class DB: + """Database""" + def __init__(self, db_module, keywords): + """Creates a database. + """ + # some DB implementaions take optional paramater `driver` to use a specific driver modue + # but it should not be passed to connect + keywords.pop('driver', None) + + self.db_module = db_module + self.keywords = keywords + + + self._ctx = threadeddict() + # flag to enable/disable printing queries + self.printing = config.get('debug', False) + self.supports_multiple_insert = False + + try: + import DBUtils + # enable pooling if DBUtils module is available. + self.has_pooling = True + except ImportError: + self.has_pooling = False + + # Pooling can be disabled by passing pooling=False in the keywords. + self.has_pooling = self.keywords.pop('pooling', True) and self.has_pooling + + def _getctx(self): + if not self._ctx.get('db'): + self._load_context(self._ctx) + return self._ctx + ctx = property(_getctx) + + def _load_context(self, ctx): + ctx.dbq_count = 0 + ctx.transactions = [] # stack of transactions + + if self.has_pooling: + ctx.db = self._connect_with_pooling(self.keywords) + else: + ctx.db = self._connect(self.keywords) + ctx.db_execute = self._db_execute + + if not hasattr(ctx.db, 'commit'): + ctx.db.commit = lambda: None + + if not hasattr(ctx.db, 'rollback'): + ctx.db.rollback = lambda: None + + def commit(unload=True): + # do db commit and release the connection if pooling is enabled. + ctx.db.commit() + if unload and self.has_pooling: + self._unload_context(self._ctx) + + def rollback(): + # do db rollback and release the connection if pooling is enabled. + ctx.db.rollback() + if self.has_pooling: + self._unload_context(self._ctx) + + ctx.commit = commit + ctx.rollback = rollback + + def _unload_context(self, ctx): + del ctx.db + + def _connect(self, keywords): + return self.db_module.connect(**keywords) + + def _connect_with_pooling(self, keywords): + def get_pooled_db(): + from DBUtils import PooledDB + + # In DBUtils 0.9.3, `dbapi` argument is renamed as `creator` + # see Bug#122112 + + if PooledDB.__version__.split('.') < '0.9.3'.split('.'): + return PooledDB.PooledDB(dbapi=self.db_module, **keywords) + else: + return PooledDB.PooledDB(creator=self.db_module, **keywords) + + if getattr(self, '_pooleddb', None) is None: + self._pooleddb = get_pooled_db() + + return self._pooleddb.connection() + + def _db_cursor(self): + return self.ctx.db.cursor() + + def _param_marker(self): + """Returns parameter marker based on paramstyle attribute if this database.""" + style = getattr(self, 'paramstyle', 'pyformat') + + if style == 'qmark': + return '?' + elif style == 'numeric': + return ':1' + elif style in ['format', 'pyformat']: + return '%s' + raise UnknownParamstyle, style + + def _db_execute(self, cur, sql_query): + """executes an sql query""" + self.ctx.dbq_count += 1 + + try: + a = time.time() + paramstyle = getattr(self, 'paramstyle', 'pyformat') + out = cur.execute(sql_query.query(paramstyle), sql_query.values()) + b = time.time() + except: + if self.printing: + print >> debug, 'ERR:', str(sql_query) + if self.ctx.transactions: + self.ctx.transactions[-1].rollback() + else: + self.ctx.rollback() + raise + + if self.printing: + print >> debug, '%s (%s): %s' % (round(b-a, 2), self.ctx.dbq_count, str(sql_query)) + return out + + def _where(self, where, vars): + if isinstance(where, (int, long)): + where = "id = " + sqlparam(where) + #@@@ for backward-compatibility + elif isinstance(where, (list, tuple)) and len(where) == 2: + where = SQLQuery(where[0], where[1]) + elif isinstance(where, SQLQuery): + pass + else: + where = reparam(where, vars) + return where + + def query(self, sql_query, vars=None, processed=False, _test=False): + """ + Execute SQL query `sql_query` using dictionary `vars` to interpolate it. + If `processed=True`, `vars` is a `reparam`-style list to use + instead of interpolating. + + >>> db = DB(None, {}) + >>> db.query("SELECT * FROM foo", _test=True) + + >>> db.query("SELECT * FROM foo WHERE x = $x", vars=dict(x='f'), _test=True) + + >>> db.query("SELECT * FROM foo WHERE x = " + sqlquote('f'), _test=True) + + """ + if vars is None: vars = {} + + if not processed and not isinstance(sql_query, SQLQuery): + sql_query = reparam(sql_query, vars) + + if _test: return sql_query + + db_cursor = self._db_cursor() + self._db_execute(db_cursor, sql_query) + + if db_cursor.description: + names = [x[0] for x in db_cursor.description] + def iterwrapper(): + row = db_cursor.fetchone() + while row: + yield storage(dict(zip(names, row))) + row = db_cursor.fetchone() + out = iterbetter(iterwrapper()) + out.__len__ = lambda: int(db_cursor.rowcount) + out.list = lambda: [storage(dict(zip(names, x))) \ + for x in db_cursor.fetchall()] + else: + out = db_cursor.rowcount + + if not self.ctx.transactions: + self.ctx.commit() + return out + + def select(self, tables, vars=None, what='*', where=None, order=None, group=None, + limit=None, offset=None, _test=False): + """ + Selects `what` from `tables` with clauses `where`, `order`, + `group`, `limit`, and `offset`. Uses vars to interpolate. + Otherwise, each clause can be a SQLQuery. + + >>> db = DB(None, {}) + >>> db.select('foo', _test=True) + + >>> db.select(['foo', 'bar'], where="foo.bar_id = bar.id", limit=5, _test=True) + + """ + if vars is None: vars = {} + sql_clauses = self.sql_clauses(what, tables, where, group, order, limit, offset) + clauses = [self.gen_clause(sql, val, vars) for sql, val in sql_clauses if val is not None] + qout = SQLQuery.join(clauses) + if _test: return qout + return self.query(qout, processed=True) + + def where(self, table, what='*', order=None, group=None, limit=None, + offset=None, _test=False, **kwargs): + """ + Selects from `table` where keys are equal to values in `kwargs`. + + >>> db = DB(None, {}) + >>> db.where('foo', bar_id=3, _test=True) + + >>> db.where('foo', source=2, crust='dewey', _test=True) + + """ + where = [] + for k, v in kwargs.iteritems(): + where.append(k + ' = ' + sqlquote(v)) + return self.select(table, what=what, order=order, + group=group, limit=limit, offset=offset, _test=_test, + where=SQLQuery.join(where, ' AND ')) + + def sql_clauses(self, what, tables, where, group, order, limit, offset): + return ( + ('SELECT', what), + ('FROM', sqllist(tables)), + ('WHERE', where), + ('GROUP BY', group), + ('ORDER BY', order), + ('LIMIT', limit), + ('OFFSET', offset)) + + def gen_clause(self, sql, val, vars): + if isinstance(val, (int, long)): + if sql == 'WHERE': + nout = 'id = ' + sqlquote(val) + else: + nout = SQLQuery(val) + #@@@ + elif isinstance(val, (list, tuple)) and len(val) == 2: + nout = SQLQuery(val[0], val[1]) # backwards-compatibility + elif isinstance(val, SQLQuery): + nout = val + else: + nout = reparam(val, vars) + + def xjoin(a, b): + if a and b: return a + ' ' + b + else: return a or b + + return xjoin(sql, nout) + + def insert(self, tablename, seqname=None, _test=False, **values): + """ + Inserts `values` into `tablename`. Returns current sequence ID. + Set `seqname` to the ID if it's not the default, or to `False` + if there isn't one. + + >>> db = DB(None, {}) + >>> q = db.insert('foo', name='bob', age=2, created=SQLLiteral('NOW()'), _test=True) + >>> q + + >>> q.query() + 'INSERT INTO foo (age, name, created) VALUES (%s, %s, NOW())' + >>> q.values() + [2, 'bob'] + """ + def q(x): return "(" + x + ")" + + if values: + _keys = SQLQuery.join(values.keys(), ', ') + _values = SQLQuery.join([sqlparam(v) for v in values.values()], ', ') + sql_query = "INSERT INTO %s " % tablename + q(_keys) + ' VALUES ' + q(_values) + else: + sql_query = SQLQuery("INSERT INTO %s DEFAULT VALUES" % tablename) + + if _test: return sql_query + + db_cursor = self._db_cursor() + if seqname is not False: + sql_query = self._process_insert_query(sql_query, tablename, seqname) + + if isinstance(sql_query, tuple): + # for some databases, a separate query has to be made to find + # the id of the inserted row. + q1, q2 = sql_query + self._db_execute(db_cursor, q1) + self._db_execute(db_cursor, q2) + else: + self._db_execute(db_cursor, sql_query) + + try: + out = db_cursor.fetchone()[0] + except Exception: + out = None + + if not self.ctx.transactions: + self.ctx.commit() + return out + + def multiple_insert(self, tablename, values, seqname=None, _test=False): + """ + Inserts multiple rows into `tablename`. The `values` must be a list of dictioanries, + one for each row to be inserted, each with the same set of keys. + Returns the list of ids of the inserted rows. + Set `seqname` to the ID if it's not the default, or to `False` + if there isn't one. + + >>> db = DB(None, {}) + >>> db.supports_multiple_insert = True + >>> values = [{"name": "foo", "email": "foo@example.com"}, {"name": "bar", "email": "bar@example.com"}] + >>> db.multiple_insert('person', values=values, _test=True) + + """ + if not values: + return [] + + if not self.supports_multiple_insert: + out = [self.insert(tablename, seqname=seqname, _test=_test, **v) for v in values] + if seqname is False: + return None + else: + return out + + keys = values[0].keys() + #@@ make sure all keys are valid + + # make sure all rows have same keys. + for v in values: + if v.keys() != keys: + raise ValueError, 'Bad data' + + sql_query = SQLQuery('INSERT INTO %s (%s) VALUES ' % (tablename, ', '.join(keys))) + + data = [] + for row in values: + d = SQLQuery.join([SQLParam(row[k]) for k in keys], ', ') + data.append('(' + d + ')') + sql_query += SQLQuery.join(data, ', ') + + if _test: return sql_query + + db_cursor = self._db_cursor() + if seqname is not False: + sql_query = self._process_insert_query(sql_query, tablename, seqname) + + if isinstance(sql_query, tuple): + # for some databases, a separate query has to be made to find + # the id of the inserted row. + q1, q2 = sql_query + self._db_execute(db_cursor, q1) + self._db_execute(db_cursor, q2) + else: + self._db_execute(db_cursor, sql_query) + + try: + out = db_cursor.fetchone()[0] + out = range(out-len(values)+1, out+1) + except Exception: + out = None + + if not self.ctx.transactions: + self.ctx.commit() + return out + + + def update(self, tables, where, vars=None, _test=False, **values): + """ + Update `tables` with clause `where` (interpolated using `vars`) + and setting `values`. + + >>> db = DB(None, {}) + >>> name = 'Joseph' + >>> q = db.update('foo', where='name = $name', name='bob', age=2, + ... created=SQLLiteral('NOW()'), vars=locals(), _test=True) + >>> q + + >>> q.query() + 'UPDATE foo SET age = %s, name = %s, created = NOW() WHERE name = %s' + >>> q.values() + [2, 'bob', 'Joseph'] + """ + if vars is None: vars = {} + where = self._where(where, vars) + + query = ( + "UPDATE " + sqllist(tables) + + " SET " + sqlwhere(values, ', ') + + " WHERE " + where) + + if _test: return query + + db_cursor = self._db_cursor() + self._db_execute(db_cursor, query) + if not self.ctx.transactions: + self.ctx.commit() + return db_cursor.rowcount + + def delete(self, table, where, using=None, vars=None, _test=False): + """ + Deletes from `table` with clauses `where` and `using`. + + >>> db = DB(None, {}) + >>> name = 'Joe' + >>> db.delete('foo', where='name = $name', vars=locals(), _test=True) + + """ + if vars is None: vars = {} + where = self._where(where, vars) + + q = 'DELETE FROM ' + table + if where: q += ' WHERE ' + where + if using: q += ' USING ' + sqllist(using) + + if _test: return q + + db_cursor = self._db_cursor() + self._db_execute(db_cursor, q) + if not self.ctx.transactions: + self.ctx.commit() + return db_cursor.rowcount + + def _process_insert_query(self, query, tablename, seqname): + return query + + def transaction(self): + """Start a transaction.""" + return Transaction(self.ctx) + +class PostgresDB(DB): + """Postgres driver.""" + def __init__(self, **keywords): + if 'pw' in keywords: + keywords['password'] = keywords.pop('pw') + + db_module = import_driver(["psycopg2", "psycopg", "pgdb"], preferred=keywords.pop('driver', None)) + if db_module.__name__ == "psycopg2": + import psycopg2.extensions + psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) + + # if db is not provided postgres driver will take it from PGDATABASE environment variable + if 'db' in keywords: + keywords['database'] = keywords.pop('db') + + self.dbname = "postgres" + self.paramstyle = db_module.paramstyle + DB.__init__(self, db_module, keywords) + self.supports_multiple_insert = True + self._sequences = None + + def _process_insert_query(self, query, tablename, seqname): + if seqname is None: + # when seqname is not provided guess the seqname and make sure it exists + seqname = tablename + "_id_seq" + if seqname not in self._get_all_sequences(): + seqname = None + + if seqname: + query += "; SELECT currval('%s')" % seqname + + return query + + def _get_all_sequences(self): + """Query postgres to find names of all sequences used in this database.""" + if self._sequences is None: + q = "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'" + self._sequences = set([c.relname for c in self.query(q)]) + return self._sequences + + def _connect(self, keywords): + conn = DB._connect(self, keywords) + conn.set_client_encoding('UTF8') + return conn + + def _connect_with_pooling(self, keywords): + conn = DB._connect_with_pooling(self, keywords) + conn._con._con.set_client_encoding('UTF8') + return conn + +class MySQLDB(DB): + def __init__(self, **keywords): + import MySQLdb as db + if 'pw' in keywords: + keywords['passwd'] = keywords['pw'] + del keywords['pw'] + + if 'charset' not in keywords: + keywords['charset'] = 'utf8' + elif keywords['charset'] is None: + del keywords['charset'] + + self.paramstyle = db.paramstyle = 'pyformat' # it's both, like psycopg + self.dbname = "mysql" + DB.__init__(self, db, keywords) + self.supports_multiple_insert = True + + def _process_insert_query(self, query, tablename, seqname): + return query, SQLQuery('SELECT last_insert_id();') + +def import_driver(drivers, preferred=None): + """Import the first available driver or preferred driver. + """ + if preferred: + drivers = [preferred] + + for d in drivers: + try: + return __import__(d, None, None, ['x']) + except ImportError: + pass + raise ImportError("Unable to import " + " or ".join(drivers)) + +class SqliteDB(DB): + def __init__(self, **keywords): + db = import_driver(["sqlite3", "pysqlite2.dbapi2", "sqlite"], preferred=keywords.pop('driver', None)) + + if db.__name__ in ["sqlite3", "pysqlite2.dbapi2"]: + db.paramstyle = 'qmark' + + self.paramstyle = db.paramstyle + keywords['database'] = keywords.pop('db') + self.dbname = "sqlite" + DB.__init__(self, db, keywords) + + def _process_insert_query(self, query, tablename, seqname): + return query, SQLQuery('SELECT last_insert_rowid();') + + def query(self, *a, **kw): + out = DB.query(self, *a, **kw) + if isinstance(out, iterbetter): + # rowcount is not provided by sqlite + def _nonzero(): + raise self.db_module.NotSupportedError("rowcount is not supported by sqlite") + del out.__len__ + out.__nonzero__ = _nonzero + return out + +class FirebirdDB(DB): + """Firebird Database. + """ + def __init__(self, **keywords): + try: + import kinterbasdb as db + except Exception: + db = None + pass + if 'pw' in keywords: + keywords['passwd'] = keywords['pw'] + del keywords['pw'] + keywords['database'] = keywords['db'] + del keywords['db'] + DB.__init__(self, db, keywords) + + def delete(self, table, where=None, using=None, vars=None, _test=False): + # firebird doesn't support using clause + using=None + return DB.delete(self, table, where, using, vars, _test) + + def sql_clauses(self, what, tables, where, group, order, limit, offset): + return ( + ('SELECT', ''), + ('FIRST', limit), + ('SKIP', offset), + ('', what), + ('FROM', sqllist(tables)), + ('WHERE', where), + ('GROUP BY', group), + ('ORDER BY', order) + ) + +class MSSQLDB(DB): + def __init__(self, **keywords): + import pymssql as db + if 'pw' in keywords: + keywords['password'] = keywords.pop('pw') + keywords['database'] = keywords.pop('db') + self.dbname = "mssql" + DB.__init__(self, db, keywords) + + def sql_clauses(self, what, tables, where, group, order, limit, offset): + return ( + ('SELECT', what), + ('TOP', limit), + ('FROM', sqllist(tables)), + ('WHERE', where), + ('GROUP BY', group), + ('ORDER BY', order), + ('OFFSET', offset)) + + def _test(self): + """Test LIMIT. + + Fake presence of pymssql module for running tests. + >>> import sys + >>> sys.modules['pymssql'] = sys.modules['sys'] + + MSSQL has TOP clause instead of LIMIT clause. + >>> db = MSSQLDB(db='test', user='joe', pw='secret') + >>> db.select('foo', limit=4, _test=True) + + """ + pass + +class OracleDB(DB): + def __init__(self, **keywords): + import cx_Oracle as db + if 'pw' in keywords: + keywords['password'] = keywords.pop('pw') + + #@@ TODO: use db.makedsn if host, port is specified + keywords['dsn'] = keywords.pop('db') + self.dbname = 'oracle' + db.paramstyle = 'numeric' + self.paramstyle = db.paramstyle + + # oracle doesn't support pooling + keywords.pop('pooling', None) + DB.__init__(self, db, keywords) + + def _process_insert_query(self, query, tablename, seqname): + if seqname is None: + # It is not possible to get seq name from table name in Oracle + return query + else: + return query + "; SELECT %s.currval FROM dual" % seqname + +_databases = {} +def database(dburl=None, **params): + """Creates appropriate database using params. + + Pooling will be enabled if DBUtils module is available. + Pooling can be disabled by passing pooling=False in params. + """ + dbn = params.pop('dbn') + if dbn in _databases: + return _databases[dbn](**params) + else: + raise UnknownDB, dbn + +def register_database(name, clazz): + """ + Register a database. + + >>> class LegacyDB(DB): + ... def __init__(self, **params): + ... pass + ... + >>> register_database('legacy', LegacyDB) + >>> db = database(dbn='legacy', db='test', user='joe', passwd='secret') + """ + _databases[name] = clazz + +register_database('mysql', MySQLDB) +register_database('postgres', PostgresDB) +register_database('sqlite', SqliteDB) +register_database('firebird', FirebirdDB) +register_database('mssql', MSSQLDB) +register_database('oracle', OracleDB) + +def _interpolate(format): + """ + Takes a format string and returns a list of 2-tuples of the form + (boolean, string) where boolean says whether string should be evaled + or not. + + from (public domain, Ka-Ping Yee) + """ + from tokenize import tokenprog + + def matchorfail(text, pos): + match = tokenprog.match(text, pos) + if match is None: + raise _ItplError(text, pos) + return match, match.end() + + namechars = "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; + chunks = [] + pos = 0 + + while 1: + dollar = format.find("$", pos) + if dollar < 0: + break + nextchar = format[dollar + 1] + + if nextchar == "{": + chunks.append((0, format[pos:dollar])) + pos, level = dollar + 2, 1 + while level: + match, pos = matchorfail(format, pos) + tstart, tend = match.regs[3] + token = format[tstart:tend] + if token == "{": + level = level + 1 + elif token == "}": + level = level - 1 + chunks.append((1, format[dollar + 2:pos - 1])) + + elif nextchar in namechars: + chunks.append((0, format[pos:dollar])) + match, pos = matchorfail(format, dollar + 1) + while pos < len(format): + if format[pos] == "." and \ + pos + 1 < len(format) and format[pos + 1] in namechars: + match, pos = matchorfail(format, pos + 1) + elif format[pos] in "([": + pos, level = pos + 1, 1 + while level: + match, pos = matchorfail(format, pos) + tstart, tend = match.regs[3] + token = format[tstart:tend] + if token[0] in "([": + level = level + 1 + elif token[0] in ")]": + level = level - 1 + else: + break + chunks.append((1, format[dollar + 1:pos])) + else: + chunks.append((0, format[pos:dollar + 1])) + pos = dollar + 1 + (nextchar == "$") + + if pos < len(format): + chunks.append((0, format[pos:])) + return chunks + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/pyulib/src/ulib/ext/web/db.pyc b/pyulib/src/ulib/ext/web/db.pyc new file mode 100644 index 0000000000000000000000000000000000000000..deaf268180a1c176c98696f8d980a29aac55c4c1 GIT binary patch literal 45431 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/debugerror.py b/pyulib/src/ulib/ext/web/debugerror.py new file mode 100644 index 0000000..59b94d3 --- /dev/null +++ b/pyulib/src/ulib/ext/web/debugerror.py @@ -0,0 +1,356 @@ +""" +pretty debug errors +(part of web.py) + +portions adapted from Django +Copyright (c) 2005, the Lawrence Journal-World +Used under the modified BSD license: +http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 +""" + +__all__ = ["debugerror", "djangoerror", "emailerrors"] + +import sys, urlparse, pprint, traceback +from net import websafe +from template import Template +from utils import sendmail +import webapi as web + +import os, os.path +whereami = os.path.join(os.getcwd(), __file__) +whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1]) +djangoerror_t = """\ +$def with (exception_type, exception_value, frames) + + + + + + $exception_type at $ctx.path + + + + + +$def dicttable (d, kls='req', id=None): + $ items = d and d.items() or [] + $items.sort() + $:dicttable_items(items, kls, id) + +$def dicttable_items(items, kls='req', id=None): + $if items: + + + $for k, v in items: + + +
VariableValue
$k
$prettify(v)
+ $else: +

No data.

+ +
+

$exception_type at $ctx.path

+

$exception_value

+ + + + + + +
Python$frames[0].filename in $frames[0].function, line $frames[0].lineno
Web$ctx.method $ctx.home$ctx.path
+
+
+ +
+$if ctx.output or ctx.headers: +

Response so far

+

HEADERS

+ $:dicttable_items(ctx.headers) + +

BODY

+

+ $ctx.output +

+ +

Request information

+ +

INPUT

+$:dicttable(web.input(_unicode=False)) + + +$:dicttable(web.cookies()) + +

META

+$ newctx = [(k, v) for (k, v) in ctx.iteritems() if not k.startswith('_') and not isinstance(v, dict)] +$:dicttable(dict(newctx)) + +

ENVIRONMENT

+$:dicttable(ctx.env) +
+ +
+

+ You're seeing this error because you have web.config.debug + set to True. Set that to False if you don't to see this. +

+
+ + + +""" + +djangoerror_r = None + +def djangoerror(): + def _get_lines_from_file(filename, lineno, context_lines): + """ + Returns context_lines before and after lineno from file. + Returns (pre_context_lineno, pre_context, context_line, post_context). + """ + try: + source = open(filename).readlines() + lower_bound = max(0, lineno - context_lines) + upper_bound = lineno + context_lines + + pre_context = \ + [line.strip('\n') for line in source[lower_bound:lineno]] + context_line = source[lineno].strip('\n') + post_context = \ + [line.strip('\n') for line in source[lineno + 1:upper_bound]] + + return lower_bound, pre_context, context_line, post_context + except (OSError, IOError): + return None, [], None, [] + + exception_type, exception_value, tback = sys.exc_info() + frames = [] + while tback is not None: + filename = tback.tb_frame.f_code.co_filename + function = tback.tb_frame.f_code.co_name + lineno = tback.tb_lineno - 1 + pre_context_lineno, pre_context, context_line, post_context = \ + _get_lines_from_file(filename, lineno, 7) + frames.append(web.storage({ + 'tback': tback, + 'filename': filename, + 'function': function, + 'lineno': lineno, + 'vars': tback.tb_frame.f_locals, + 'id': id(tback), + 'pre_context': pre_context, + 'context_line': context_line, + 'post_context': post_context, + 'pre_context_lineno': pre_context_lineno, + })) + tback = tback.tb_next + frames.reverse() + urljoin = urlparse.urljoin + def prettify(x): + try: + out = pprint.pformat(x) + except Exception, e: + out = '[could not display: <' + e.__class__.__name__ + \ + ': '+str(e)+'>]' + return out + + global djangoerror_r + if djangoerror_r is None: + djangoerror_r = Template(djangoerror_t, filename=__file__, filter=websafe) + + t = djangoerror_r + globals = {'ctx': web.ctx, 'web':web, 'dict':dict, 'str':str, 'prettify': prettify} + t.t.func_globals.update(globals) + return t(exception_type, exception_value, frames) + +def debugerror(): + """ + A replacement for `internalerror` that presents a nice page with lots + of debug information for the programmer. + + (Based on the beautiful 500 page from [Django](http://djangoproject.com/), + designed by [Wilson Miner](http://wilsonminer.com/).) + """ + return web._InternalError(djangoerror()) + +def emailerrors(to_address, olderror, from_address=None): + """ + Wraps the old `internalerror` handler (pass as `olderror`) to + additionally email all errors to `to_address`, to aid in + debugging production websites. + + Emails contain a normal text traceback as well as an + attachment containing the nice `debugerror` page. + """ + from_address = from_address or to_address + + def emailerrors_internal(): + error = olderror() + tb = sys.exc_info() + error_name = tb[0] + error_value = tb[1] + tb_txt = ''.join(traceback.format_exception(*tb)) + path = web.ctx.path + request = web.ctx.method + ' ' + web.ctx.home + web.ctx.fullpath + text = ("""\ +------here---- +Content-Type: text/plain +Content-Disposition: inline + +%(request)s + +%(tb_txt)s + +------here---- +Content-Type: text/html; name="bug.html" +Content-Disposition: attachment; filename="bug.html" + +""" % locals()) + str(djangoerror()) + sendmail( + "your buggy site <%s>" % from_address, + "the bugfixer <%s>" % to_address, + "bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(), + text, + headers={'Content-Type': 'multipart/mixed; boundary="----here----"'}) + return error + + return emailerrors_internal + +if __name__ == "__main__": + urls = ( + '/', 'index' + ) + from application import application + app = application(urls, globals()) + app.internalerror = debugerror + + class index: + def GET(self): + thisdoesnotexist + + app.run() diff --git a/pyulib/src/ulib/ext/web/debugerror.pyc b/pyulib/src/ulib/ext/web/debugerror.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51ea2f0f132e4dd4a09f937c4e560d9057bb18da GIT binary patch literal 13325 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 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/form.py b/pyulib/src/ulib/ext/web/form.py new file mode 100644 index 0000000..17984dc --- /dev/null +++ b/pyulib/src/ulib/ext/web/form.py @@ -0,0 +1,367 @@ +""" +HTML forms +(part of web.py) +""" + +import copy, re +import webapi as web +import utils, net + +def attrget(obj, attr, value=None): + if hasattr(obj, 'has_key') and obj.has_key(attr): return obj[attr] + if hasattr(obj, attr): return getattr(obj, attr) + return value + +class Form: + r""" + HTML form. + + >>> f = Form(Textbox("x")) + >>> f.render() + '\n \n
' + """ + def __init__(self, *inputs, **kw): + self.inputs = inputs + self.valid = True + self.note = None + self.validators = kw.pop('validators', []) + + def __call__(self, x=None): + o = copy.deepcopy(self) + if x: o.validates(x) + return o + + def render(self): + out = '' + out += self.rendernote(self.note) + out += '\n' + + for i in self.inputs: + html = i.pre + i.render() + self.rendernote(i.note) + i.post + if i.is_hidden(): + out += ' \n' % (html) + else: + out += ' \n' % (i.id, net.websafe(i.description), html) + out += "
%s
%s
" + return out + + def render_css(self): + out = [] + out.append(self.rendernote(self.note)) + for i in self.inputs: + if not i.is_hidden(): + out.append('' % (i.id, net.websafe(i.description))) + out.append(i.pre) + out.append(i.render()) + out.append(self.rendernote(i.note)) + out.append(i.post) + out.append('\n') + return ''.join(out) + + def rendernote(self, note): + if note: return '%s' % net.websafe(note) + else: return "" + + def validates(self, source=None, _validate=True, **kw): + source = source or kw or web.input() + out = True + for i in self.inputs: + v = attrget(source, i.name) + if _validate: + out = i.validate(v) and out + else: + i.value = v + if _validate: + out = out and self._validate(source) + self.valid = out + return out + + def _validate(self, value): + self.value = value + for v in self.validators: + if not v.valid(value): + self.note = v.msg + return False + return True + + def fill(self, source=None, **kw): + return self.validates(source, _validate=False, **kw) + + def __getitem__(self, i): + for x in self.inputs: + if x.name == i: return x + raise KeyError, i + + def __getattr__(self, name): + # don't interfere with deepcopy + inputs = self.__dict__.get('inputs') or [] + for x in inputs: + if x.name == name: return x + raise AttributeError, name + + def get(self, i, default=None): + try: + return self[i] + except KeyError: + return default + + def _get_d(self): #@@ should really be form.attr, no? + return utils.storage([(i.name, i.get_value()) for i in self.inputs]) + d = property(_get_d) + +class Input(object): + def __init__(self, name, *validators, **attrs): + self.name = name + self.validators = validators + self.attrs = attrs = AttributeList(attrs) + + self.description = attrs.pop('description', name) + self.value = attrs.pop('value', None) + self.pre = attrs.pop('pre', "") + self.post = attrs.pop('post', "") + self.note = None + + self.id = attrs.setdefault('id', self.get_default_id()) + + if 'class_' in attrs: + attrs['class'] = attrs['class_'] + del attrs['class_'] + + def is_hidden(self): + return False + + def get_type(self): + raise NotImplementedError + + def get_default_id(self): + return self.name + + def validate(self, value): + self.set_value(value) + + for v in self.validators: + if not v.valid(value): + self.note = v.msg + return False + return True + + def set_value(self, value): + self.value = value + + def get_value(self): + return self.value + + def render(self): + attrs = self.attrs.copy() + attrs['type'] = self.get_type() + if self.value: + attrs['value'] = self.value + attrs['name'] = self.name + return '' % attrs + + def rendernote(self, note): + if note: return '%s' % net.websafe(note) + else: return "" + + def addatts(self): + return str(self.attrs) + +class AttributeList(dict): + """List of atributes of input. + + >>> a = AttributeList(type='text', name='x', value=20) + >>> a + + """ + def copy(self): + return AttributeList(self) + + def __str__(self): + return " ".join('%s="%s"' % (k, net.websafe(v)) for k, v in self.items()) + + def __repr__(self): + return '' % repr(str(self)) + +class Textbox(Input): + """Textbox input. + + >>> Textbox(name='foo', value='bar').render() + '' + """ + def get_type(self): + return 'text' + +class Password(Input): + """Password input. + + >>> Password(name='password', value='secret').render() + '' + """ + + def get_type(self): + return 'password' + +class Textarea(Input): + """Textarea input. + + >>> Textarea(name='foo', value='bar').render() + '' + """ + def render(self): + attrs = self.attrs.copy() + attrs['name'] = self.name + value = net.websafe(self.value or '') + return '' % (attrs, value) + +class Dropdown(Input): + r"""Dropdown/select input. + + >>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render() + '\n' + >>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], value='b').render() + '\n' + """ + def __init__(self, name, args, *validators, **attrs): + self.args = args + super(Dropdown, self).__init__(name, *validators, **attrs) + + def render(self): + attrs = self.attrs.copy() + attrs['name'] = self.name + + x = '\n' + return x + +class Radio(Input): + def __init__(self, name, args, *validators, **attrs): + self.args = args + super(Radio, self).__init__(name, *validators, **attrs) + + def render(self): + x = '' + for arg in self.args: + if isinstance(arg, (tuple, list)): + value, desc= arg + else: + value, desc = arg, arg + attrs = self.attrs.copy() + attrs['name'] = self.name + attrs['type'] = 'radio' + attrs['value'] = arg + if self.value == arg: + attrs['checked'] = 'checked' + x += ' %s' % (attrs, net.websafe(desc)) + x += '' + return x + +class Checkbox(Input): + """Checkbox input. + + >>> Checkbox('foo', value='bar', checked=True).render() + '' + >>> Checkbox('foo', value='bar').render() + '' + >>> c = Checkbox('foo', value='bar') + >>> c.validate('on') + True + >>> c.render() + '' + """ + def __init__(self, name, *validators, **attrs): + self.checked = attrs.pop('checked', False) + Input.__init__(self, name, *validators, **attrs) + + def get_default_id(self): + value = self.value or "" + return self.name + '_' + value.replace(' ', '_') + + def render(self): + attrs = self.attrs.copy() + attrs['type'] = 'checkbox' + attrs['name'] = self.name + attrs['value'] = self.value + + if self.checked: + attrs['checked'] = 'checked' + return '' % attrs + + def set_value(self, value): + if value: + self.checked = True + + def get_value(self): + return self.checked + +class Button(Input): + """HTML Button. + + >>> Button("save").render() + '' + >>> Button("action", value="save", html="Save Changes").render() + '' + """ + def __init__(self, name, *validators, **attrs): + super(Button, self).__init__(name, *validators, **attrs) + self.description = "" + + def render(self): + attrs = self.attrs.copy() + attrs['name'] = self.name + if self.value is not None: + attrs['value'] = self.value + html = attrs.pop('html', None) or net.websafe(self.name) + return '' % (attrs, html) + +class Hidden(Input): + """Hidden Input. + + >>> Hidden(name='foo', value='bar').render() + '' + """ + def is_hidden(self): + return True + + def get_type(self): + return 'hidden' + +class File(Input): + """File input. + + >>> File(name='f').render() + '' + """ + def get_type(self): + return 'file' + +class Validator: + def __deepcopy__(self, memo): return copy.copy(self) + def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals()) + def valid(self, value): + try: return self.test(value) + except: return False + +notnull = Validator("Required", bool) + +class regexp(Validator): + def __init__(self, rexp, msg): + self.rexp = re.compile(rexp) + self.msg = msg + + def valid(self, value): + return bool(self.rexp.match(value)) + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/pyulib/src/ulib/ext/web/form.pyc b/pyulib/src/ulib/ext/web/form.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6c224ba746fe996b8ce7adfca4b3cedee19fcad GIT binary patch literal 18878 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 ImeA{e0ehM)-T(jq literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/http.py b/pyulib/src/ulib/ext/web/http.py new file mode 100644 index 0000000..acac451 --- /dev/null +++ b/pyulib/src/ulib/ext/web/http.py @@ -0,0 +1,148 @@ +""" +HTTP Utilities +(from web.py) +""" + +__all__ = [ + "expires", "lastmodified", + "prefixurl", "modified", + "changequery", "url", + "profiler", +] + +import sys, os, threading, urllib, urlparse +try: import datetime +except ImportError: pass +import net, utils, webapi as web + +def prefixurl(base=''): + """ + Sorry, this function is really difficult to explain. + Maybe some other time. + """ + url = web.ctx.path.lstrip('/') + for i in xrange(url.count('/')): + base += '../' + if not base: + base = './' + return base + +def expires(delta): + """ + Outputs an `Expires` header for `delta` from now. + `delta` is a `timedelta` object or a number of seconds. + """ + if isinstance(delta, (int, long)): + delta = datetime.timedelta(seconds=delta) + date_obj = datetime.datetime.utcnow() + delta + web.header('Expires', net.httpdate(date_obj)) + +def lastmodified(date_obj): + """Outputs a `Last-Modified` header for `datetime`.""" + web.header('Last-Modified', net.httpdate(date_obj)) + +def modified(date=None, etag=None): + """ + Checks to see if the page has been modified since the version in the + requester's cache. + + When you publish pages, you can include `Last-Modified` and `ETag` + with the date the page was last modified and an opaque token for + the particular version, respectively. When readers reload the page, + the browser sends along the modification date and etag value for + the version it has in its cache. If the page hasn't changed, + the server can just return `304 Not Modified` and not have to + send the whole page again. + + This function takes the last-modified date `date` and the ETag `etag` + and checks the headers to see if they match. If they do, it returns + `True` and sets the response status to `304 Not Modified`. It also + sets `Last-Modified and `ETag` output headers. + """ + try: + from __builtin__ import set + except ImportError: + # for python 2.3 + from sets import Set as set + + n = set([x.strip('" ') for x in web.ctx.env.get('HTTP_IF_NONE_MATCH', '').split(',')]) + m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0]) + validate = False + if etag: + if '*' in n or etag in n: + validate = True + if date and m: + # we subtract a second because + # HTTP dates don't have sub-second precision + if date-datetime.timedelta(seconds=1) <= m: + validate = True + + if validate: web.ctx.status = '304 Not Modified' + if date: lastmodified(date) + if etag: web.header('ETag', '"' + etag + '"') + return not validate + +def urlencode(query, doseq=0): + """ + Same as urllib.urlencode, but supports unicode strings. + + >>> urlencode({'text':'foo bar'}) + 'text=foo+bar' + >>> urlencode({'x': [1, 2]}, doseq=True) + 'x=1&x=2' + """ + def convert(value, doseq=False): + if doseq and isinstance(value, list): + return [convert(v) for v in value] + else: + return utils.utf8(value) + + query = dict([(k, convert(v, doseq)) for k, v in query.items()]) + return urllib.urlencode(query, doseq=doseq) + +def changequery(query=None, **kw): + """ + Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return + `/foo?a=3&b=2` -- the same URL but with the arguments you requested + changed. + """ + if query is None: + query = web.rawinput(method='get') + for k, v in kw.iteritems(): + if v is None: + query.pop(k, None) + else: + query[k] = v + out = web.ctx.path + if query: + out += '?' + urlencode(query, doseq=True) + return out + +def url(path=None, **kw): + """ + Makes url by concatinating web.ctx.homepath and path and the + query string created using the arguments. + """ + if path is None: + path = web.ctx.path + if path.startswith("/"): + out = web.ctx.homepath + path + else: + out = path + + if kw: + out += '?' + urlencode(kw) + + return out + +def profiler(app): + """Outputs basic profiling information at the bottom of each response.""" + from utils import profile + def profile_internal(e, o): + out, result = profile(app)(e, o) + return list(out) + ['
' + net.websafe(result) + '
'] + return profile_internal + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/pyulib/src/ulib/ext/web/http.pyc b/pyulib/src/ulib/ext/web/http.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5db8e792aecf06f7144023a89a9fc576896cf254 GIT binary patch literal 6273 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 H4$=3272%r> literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/httpserver.py b/pyulib/src/ulib/ext/web/httpserver.py new file mode 100644 index 0000000..8352db9 --- /dev/null +++ b/pyulib/src/ulib/ext/web/httpserver.py @@ -0,0 +1,265 @@ +__all__ = ["runsimple"] + +import sys, os +from os import path +import urlparse, posixpath, urllib +from SimpleHTTPServer import SimpleHTTPRequestHandler + +import webapi as web +import net +import utils + +def runbasic(func, server_address=("0.0.0.0", 8080)): + """ + Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` + is hosted statically. + + Based on [WsgiServer][ws] from [Colin Stewart][cs]. + + [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html + [cs]: http://www.owlfish.com/ + """ + # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) + # Modified somewhat for simplicity + # Used under the modified BSD license: + # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 + + import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse + import socket, errno + import traceback + + class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def run_wsgi_app(self): + protocol, host, path, parameters, query, fragment = \ + urlparse.urlparse('http://dummyhost%s' % self.path) + + # we only use path, query + env = {'wsgi.version': (1, 0) + ,'wsgi.url_scheme': 'http' + ,'wsgi.input': self.rfile + ,'wsgi.errors': sys.stderr + ,'wsgi.multithread': 1 + ,'wsgi.multiprocess': 0 + ,'wsgi.run_once': 0 + ,'REQUEST_METHOD': self.command + ,'REQUEST_URI': self.path + ,'PATH_INFO': path + ,'QUERY_STRING': query + ,'CONTENT_TYPE': self.headers.get('Content-Type', '') + ,'CONTENT_LENGTH': self.headers.get('Content-Length', '') + ,'REMOTE_ADDR': self.client_address[0] + ,'SERVER_NAME': self.server.server_address[0] + ,'SERVER_PORT': str(self.server.server_address[1]) + ,'SERVER_PROTOCOL': self.request_version + } + + for http_header, http_value in self.headers.items(): + env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \ + http_value + + # Setup the state + self.wsgi_sent_headers = 0 + self.wsgi_headers = [] + + try: + # We have there environment, now invoke the application + result = self.server.app(env, self.wsgi_start_response) + try: + try: + for data in result: + if data: + self.wsgi_write_data(data) + finally: + if hasattr(result, 'close'): + result.close() + except socket.error, socket_err: + # Catch common network errors and suppress them + if (socket_err.args[0] in \ + (errno.ECONNABORTED, errno.EPIPE)): + return + except socket.timeout, socket_timeout: + return + except: + print >> web.debug, traceback.format_exc(), + + if (not self.wsgi_sent_headers): + # We must write out something! + self.wsgi_write_data(" ") + return + + do_POST = run_wsgi_app + do_PUT = run_wsgi_app + do_DELETE = run_wsgi_app + + def do_GET(self): + if self.path.startswith('/static/'): + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + else: + self.run_wsgi_app() + + def wsgi_start_response(self, response_status, response_headers, + exc_info=None): + if (self.wsgi_sent_headers): + raise Exception \ + ("Headers already sent and start_response called again!") + # Should really take a copy to avoid changes in the application.... + self.wsgi_headers = (response_status, response_headers) + return self.wsgi_write_data + + def wsgi_write_data(self, data): + if (not self.wsgi_sent_headers): + status, headers = self.wsgi_headers + # Need to send header prior to data + status_code = status[:status.find(' ')] + status_msg = status[status.find(' ') + 1:] + self.send_response(int(status_code), status_msg) + for header, value in headers: + self.send_header(header, value) + self.end_headers() + self.wsgi_sent_headers = 1 + # Send the data + self.wfile.write(data) + + class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + def __init__(self, func, server_address): + BaseHTTPServer.HTTPServer.__init__(self, + server_address, + WSGIHandler) + self.app = func + self.serverShuttingDown = 0 + + #print "http://%s:%d/" % server_address + WSGIServer(func, server_address).serve_forever() + +def runsimple(func, server_address=("0.0.0.0", 8080)): + """ + Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. + The directory `static/` is hosted statically. + + [cp]: http://www.cherrypy.org + """ + func = StaticMiddleware(func) + func = LogMiddleware(func) + + server = WSGIServer(server_address, func) + + #print "http://%s:%d/" % server_address + try: + server.start() + except KeyboardInterrupt: + server.stop() + +def WSGIServer(server_address, wsgi_app): + """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`. + This function can be overwritten to customize the webserver or use a different webserver. + """ + from wsgiserver import CherryPyWSGIServer + return CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost") + +class StaticApp(SimpleHTTPRequestHandler): + """WSGI application for serving static files.""" + def __init__(self, environ, start_response): + self.headers = [] + self.environ = environ + self.start_response = start_response + + def translate_path(self, path): + path = urlparse.urlparse(path)[2] + path = posixpath.normpath(urllib.unquote(path)) + words = path.split('/') + words = filter(None, words) + path = web.config.get('BASEDIR', os.getcwd()) + for word in words: + _, word = os.path.splitdrive(word) + _, word = os.path.split(word) + if word in (os.curdir, os.pardir): continue + path = os.path.join(path, word) + return path + + def send_response(self, status, msg=""): + self.status = str(status) + " " + msg + + def send_header(self, name, value): + self.headers.append((name, value)) + + def end_headers(self): + pass + + def log_message(*a): pass + + def __iter__(self): + environ = self.environ + + self.path = environ.get('PATH_INFO', '') + self.client_address = environ.get('REMOTE_ADDR','-'), \ + environ.get('REMOTE_PORT','-') + self.command = environ.get('REQUEST_METHOD', '-') + + from cStringIO import StringIO + self.wfile = StringIO() # for capturing error + + f = self.send_head() + self.start_response(self.status, self.headers) + + if f: + block_size = 16 * 1024 + while True: + buf = f.read(block_size) + if not buf: + break + yield buf + f.close() + else: + value = self.wfile.getvalue() + yield value + +class StaticMiddleware: + """WSGI middleware for serving static files.""" + def __init__(self, app, prefix='/static/'): + self.app = app + self.prefix = prefix + + def __call__(self, environ, start_response): + path = environ.get('PATH_INFO', '') + if path.startswith(self.prefix): + return StaticApp(environ, start_response) + else: + return self.app(environ, start_response) + +class LogMiddleware: + """WSGI middleware for logging the status.""" + def __init__(self, app): + self.app = app + self.format = '%s - - [%s] "%s %s %s" - %s' + + from BaseHTTPServer import BaseHTTPRequestHandler + import StringIO + f = StringIO.StringIO() + + class FakeSocket: + def makefile(self, *a): + return f + + # take log_date_time_string method from BaseHTTPRequestHandler + self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string + + def __call__(self, environ, start_response): + def xstart_response(status, response_headers, *args): + out = start_response(status, response_headers, *args) + self.log(status, environ) + return out + + return self.app(environ, xstart_response) + + def log(self, status, environ): + outfile = environ.get('wsgi.errors', web.debug) + req = environ.get('PATH_INFO', '_') + protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-') + method = environ.get('REQUEST_METHOD', '-') + host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), + environ.get('REMOTE_PORT','-')) + + time = self.log_date_time_string() + + msg = self.format % (host, time, protocol, method, req, status) + print >> outfile, utils.safestr(msg) diff --git a/pyulib/src/ulib/ext/web/httpserver.pyc b/pyulib/src/ulib/ext/web/httpserver.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4ea929ab432ab3f4ad8c4df6f95e593af4c7265 GIT binary patch literal 11654 zcmcIqOKcq3T0T|nw%zS^Z0F&`^K534x#lvl2M{AMTrQK?J&7+#+nnyiGfqOeQ&nwO z+}+iwQytrL;|+->2?FU#ydUDRK}hUCAR(bEcC6VU7VHob8xRYSSODMmpQ^T#nXt0m zKBvz)b)Ns@`~UM-|NVbVmTy=8cFkAW-z2_2MKb@2EKq98Q5z^YDsa^f$|%^ft2SII z7u1fcIt8^+P)_!2L~V@Z{fcU%n3qS@#;BA>)XtddjH`|D;h2)zDChkq)W$?!uBeTQ z3Z_*srk-N`NfpefU|hG0toH0kwL zFX)7c8NPX=Au&hP zi{aR!dNJ%js$LXSP*Beu6^)S$&qmZUrJkYSs%Hh&1`9^iSC%j@P${a%u8PLh^HCL* zR2!W})w3cC!D!y^pr9V(U#Z8AdV=0%lGPDv@sQ>{kfvP4p9aaQt*Rwkl{BfEFnJUv z)$Q1%QE#id-nh0>ZT0)r&Gw+@Z!T1u+hH|`lF(1%WWTy;(pDPzianl;zRYmO*petKL~RTTw$+xp!yR+^e>exLdum6nCOtwULIqtt7p7$2a#DWE?W~ z`lPy@ru|PA7k77e7vkMcJ2Kk~e%xI&aXZD3aB&yY4$`P&7K7Lybi*EZ7Wbt6vOU%1 zRzF(UPP-jWiWOcvsjO_pD4hVsNQF#Hd{pI+l0gYEw~>XAWw5Luj1vyF!Fol8K-og5 zTtV^?$%|wzMc$DP5EyiJMKi`J3{Nqtq;zAxh^c+bF-33&=?s#&j%A-0R3rigE=P`< zed4IQqv}9J5$r!0Q3pkJFsinpIPY-&k0CjbkdN6PvNEPVPB8`69mgXyl<);fEZeUM z+Yv)5wuH@2qJ$o&@P+m%+YTul6D6Oaq(Nt80ow{~wXJ7utDJ2vIA?1m$|aVZ3udvG z35qKOnr*I{!*PQ1wjYq-g8KMiOzr)w^ugSdl;`DpAF8{qIv7_6CG{Bp*vFiDJgy=D z#TzPkQ$&A?U1z`Zu;8p?Ev(^9t1T!73Nvc|@A6eob2xW!hNBM3YQLerysY*

;)4 z=^b@|4NjAj&G8T+P;EpzN-_6!r_spF(? zspxI20DXUltGX*(_j7J{TJ?t;58jrIb4#3h9zdt+<}~t5!Gb}zyHEAMWFS}=jigKk z88vJH;9#Iu0<0Wirt&9)q~jTXJM4z0oVP{2{vb6}AsHMdNt_rno!7gAP8y}#N!SVu z#alW_V?T-g&=^CjCapksFYftaN>$LcU$|9kG`*X(=JnNOQPI5VmR=ESuy&<+-CL=D zwwiM5=℞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 literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/net.py b/pyulib/src/ulib/ext/web/net.py new file mode 100644 index 0000000..6c3ee85 --- /dev/null +++ b/pyulib/src/ulib/ext/web/net.py @@ -0,0 +1,190 @@ +""" +Network Utilities +(from web.py) +""" + +__all__ = [ + "validipaddr", "validipport", "validip", "validaddr", + "urlquote", + "httpdate", "parsehttpdate", + "htmlquote", "htmlunquote", "websafe", +] + +import urllib, time +try: import datetime +except ImportError: pass + +def validipaddr(address): + """ + Returns True if `address` is a valid IPv4 address. + + >>> validipaddr('192.168.1.1') + True + >>> validipaddr('192.168.1.800') + False + >>> validipaddr('192.168.1') + False + """ + try: + octets = address.split('.') + if len(octets) != 4: + return False + for x in octets: + if not (0 <= int(x) <= 255): + return False + except ValueError: + return False + return True + +def validipport(port): + """ + Returns True if `port` is a valid IPv4 port. + + >>> validipport('9000') + True + >>> validipport('foo') + False + >>> validipport('1000000') + False + """ + try: + if not (0 <= int(port) <= 65535): + return False + except ValueError: + return False + return True + +def validip(ip, defaultaddr="0.0.0.0", defaultport=8080): + """Returns `(ip_address, port)` from string `ip_addr_port`""" + addr = defaultaddr + port = defaultport + + ip = ip.split(":", 1) + if len(ip) == 1: + if not ip[0]: + pass + elif validipaddr(ip[0]): + addr = ip[0] + elif validipport(ip[0]): + port = int(ip[0]) + else: + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + elif len(ip) == 2: + addr, port = ip + if not validipaddr(addr) and validipport(port): + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + port = int(port) + else: + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + return (addr, port) + +def validaddr(string_): + """ + Returns either (ip_address, port) or "/path/to/socket" from string_ + + >>> validaddr('/path/to/socket') + '/path/to/socket' + >>> validaddr('8000') + ('0.0.0.0', 8000) + >>> validaddr('127.0.0.1') + ('127.0.0.1', 8080) + >>> validaddr('127.0.0.1:8000') + ('127.0.0.1', 8000) + >>> validaddr('fff') + Traceback (most recent call last): + ... + ValueError: fff is not a valid IP address/port + """ + if '/' in string_: + return string_ + else: + return validip(string_) + +def urlquote(val): + """ + Quotes a string for use in a URL. + + >>> urlquote('://?f=1&j=1') + '%3A//%3Ff%3D1%26j%3D1' + >>> urlquote(None) + '' + >>> urlquote(u'\u203d') + '%E2%80%BD' + """ + if val is None: return '' + if not isinstance(val, unicode): val = str(val) + else: val = val.encode('utf-8') + return urllib.quote(val) + +def httpdate(date_obj): + """ + Formats a datetime object for use in HTTP headers. + + >>> import datetime + >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1)) + 'Thu, 01 Jan 1970 01:01:01 GMT' + """ + return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT") + +def parsehttpdate(string_): + """ + Parses an HTTP date into a datetime object. + + >>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT') + datetime.datetime(1970, 1, 1, 1, 1, 1) + """ + try: + t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z") + except ValueError: + return None + return datetime.datetime(*t[:6]) + +def htmlquote(text): + """ + Encodes `text` for raw use in HTML. + + >>> htmlquote("<'&\\">") + '<'&">' + """ + text = text.replace("&", "&") # Must be done first! + text = text.replace("<", "<") + text = text.replace(">", ">") + text = text.replace("'", "'") + text = text.replace('"', """) + return text + +def htmlunquote(text): + """ + Decodes `text` that's HTML quoted. + + >>> htmlunquote('<'&">') + '<\\'&">' + """ + text = text.replace(""", '"') + text = text.replace("'", "'") + text = text.replace(">", ">") + text = text.replace("<", "<") + text = text.replace("&", "&") # Must be done last! + return text + +def websafe(val): + """ + Converts `val` so that it's safe for use in UTF-8 HTML. + + >>> websafe("<'&\\">") + '<'&">' + >>> websafe(None) + '' + >>> websafe(u'\u203d') + '\\xe2\\x80\\xbd' + """ + if val is None: + return '' + if isinstance(val, unicode): + val = val.encode('utf-8') + val = str(val) + return htmlquote(val) + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/pyulib/src/ulib/ext/web/net.pyc b/pyulib/src/ulib/ext/web/net.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f26bf9a836f7b8dff2e9b5c5d9a50991c5a3122e GIT binary patch literal 5982 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 XW3y8iP)?8K$0o)m@jE+#tLuLNqk;NK literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/session.py b/pyulib/src/ulib/ext/web/session.py new file mode 100644 index 0000000..a260318 --- /dev/null +++ b/pyulib/src/ulib/ext/web/session.py @@ -0,0 +1,319 @@ +""" +Session Management +(from web.py) +""" + +import os, time, datetime, random, base64 +try: + import cPickle as pickle +except ImportError: + import pickle +try: + import hashlib + sha1 = hashlib.sha1 +except ImportError: + import sha + sha1 = sha.new + +import utils +import webapi as web + +__all__ = [ + 'Session', 'SessionExpired', + 'Store', 'DiskStore', 'DBStore', +] + +web.config.session_parameters = utils.storage({ + 'cookie_name': 'webpy_session_id', + 'cookie_domain': None, + 'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds + 'ignore_expiry': True, + 'ignore_change_ip': True, + 'secret_key': 'fLjUfxqXtfNoIldA0A0J', + 'expired_message': 'Session expired', +}) + +class SessionExpired(web.HTTPError): + def __init__(self, message): + web.HTTPError.__init__(self, '200 OK', {}, data=message) + +class Session(utils.ThreadedDict): + """Session management for web.py + """ + + def __init__(self, app, store, initializer=None): + self.__dict__['store'] = store + self.__dict__['_initializer'] = initializer + self.__dict__['_last_cleanup_time'] = 0 + self.__dict__['_config'] = utils.storage(web.config.session_parameters) + + if app: + app.add_processor(self._processor) + + def _processor(self, handler): + """Application processor to setup session for every request""" + self._cleanup() + self._load() + + try: + return handler() + finally: + self._save() + + def _load(self): + """Load the session from the store, by the id from cookie""" + cookie_name = self._config.cookie_name + cookie_domain = self._config.cookie_domain + self.session_id = web.cookies().get(cookie_name) + + # protection against session_id tampering + if self.session_id and not self._valid_session_id(self.session_id): + self.session_id = None + + self._check_expiry() + if self.session_id: + d = self.store[self.session_id] + self.update(d) + self._validate_ip() + + if not self.session_id: + self.session_id = self._generate_session_id() + + if self._initializer: + if isinstance(self._initializer, dict): + self.update(self._initializer) + elif hasattr(self._initializer, '__call__'): + self._initializer() + + self.ip = web.ctx.ip + + def _check_expiry(self): + # check for expiry + if self.session_id and self.session_id not in self.store: + if self._config.ignore_expiry: + self.session_id = None + else: + return self.expired() + + def _validate_ip(self): + # check for change of IP + if self.session_id and self.get('ip', None) != web.ctx.ip: + if not self._config.ignore_change_ip: + return self.expired() + + def _save(self): + cookie_name = self._config.cookie_name + cookie_domain = self._config.cookie_domain + if not self.get('_killed'): + web.setcookie(cookie_name, self.session_id, domain=cookie_domain) + self.store[self.session_id] = dict(self) + else: + web.setcookie(cookie_name, self.session_id, expires=-1, domain=cookie_domain) + + def _generate_session_id(self): + """Generate a random id for session""" + + while True: + rand = os.urandom(16) + now = time.time() + secret_key = self._config.secret_key + session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key)) + session_id = session_id.hexdigest() + if session_id not in self.store: + break + return session_id + + def _valid_session_id(self, session_id): + rx = utils.re_compile('^[0-9a-fA-F]+$') + return rx.match(session_id) + + def _cleanup(self): + """Cleanup the stored sessions""" + current_time = time.time() + timeout = self._config.timeout + if current_time - self._last_cleanup_time > timeout: + self.store.cleanup(timeout) + self.__dict__['_last_cleanup_time'] = current_time + + def expired(self): + """Called when an expired session is atime""" + self._killed = True + self._save() + raise SessionExpired(self._config.expired_message) + + def kill(self): + """Kill the session, make it no longer available""" + del self.store[self.session_id] + self._killed = True + +class Store: + """Base class for session stores""" + + def __contains__(self, key): + raise NotImplementedError + + def __getitem__(self, key): + raise NotImplementedError + + def __setitem__(self, key, value): + raise NotImplementedError + + def cleanup(self, timeout): + """removes all the expired sessions""" + raise NotImplementedError + + def encode(self, session_dict): + """encodes session dict as a string""" + pickled = pickle.dumps(session_dict) + return base64.encodestring(pickled) + + def decode(self, session_data): + """decodes the data to get back the session dict """ + pickled = base64.decodestring(session_data) + return pickle.loads(pickled) + +class DiskStore(Store): + """ + Store for saving a session on disk. + + >>> import tempfile + >>> root = tempfile.mkdtemp() + >>> s = DiskStore(root) + >>> s['a'] = 'foo' + >>> s['a'] + 'foo' + >>> time.sleep(0.01) + >>> s.cleanup(0.01) + >>> s['a'] + Traceback (most recent call last): + ... + KeyError: 'a' + """ + def __init__(self, root): + # if the storage root doesn't exists, create it. + if not os.path.exists(root): + os.mkdir(root) + self.root = root + + def _get_path(self, key): + if os.path.sep in key: + raise ValueError, "Bad key: %s" % repr(key) + return os.path.join(self.root, key) + + def __contains__(self, key): + path = self._get_path(key) + return os.path.exists(path) + + def __getitem__(self, key): + path = self._get_path(key) + if os.path.exists(path): + pickled = open(path).read() + return self.decode(pickled) + else: + raise KeyError, key + + def __setitem__(self, key, value): + path = self._get_path(key) + pickled = self.encode(value) + try: + f = open(path, 'w') + try: + f.write(pickled) + finally: + f.close() + except IOError: + pass + + def __delitem__(self, key): + path = self._get_path(key) + if os.path.exists(path): + os.remove(path) + + def cleanup(self, timeout): + now = time.time() + for f in os.listdir(self.root): + path = self._get_path(f) + atime = os.stat(path).st_atime + if now - atime > timeout : + os.remove(path) + +class DBStore(Store): + """Store for saving a session in database + Needs a table with the following columns: + + session_id CHAR(128) UNIQUE NOT NULL, + atime DATETIME NOT NULL default current_timestamp, + data TEXT + """ + def __init__(self, db, table_name): + self.db = db + self.table = table_name + + def __contains__(self, key): + data = self.db.select(self.table, where="session_id=$key", vars=locals()) + return bool(list(data)) + + def __getitem__(self, key): + now = datetime.datetime.now() + try: + s = self.db.select(self.table, where="session_id=$key", vars=locals())[0] + self.db.update(self.table, where="session_id=$key", atime=now, vars=locals()) + except IndexError: + raise KeyError + else: + return self.decode(s.data) + + def __setitem__(self, key, value): + pickled = self.encode(value) + now = datetime.datetime.now() + if key in self: + self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals()) + else: + self.db.insert(self.table, False, session_id=key, data=pickled ) + + def __delitem__(self, key): + self.db.delete(self.table, where="session_id=$key", vars=locals()) + + def cleanup(self, timeout): + timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg + last_allowed_time = datetime.datetime.now() - timeout + self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals()) + +class ShelfStore: + """Store for saving session using `shelve` module. + + import shelve + store = ShelfStore(shelve.open('session.shelf')) + + XXX: is shelve thread-safe? + """ + def __init__(self, shelf): + self.shelf = shelf + + def __contains__(self, key): + return key in self.shelf + + def __getitem__(self, key): + atime, v = self.shelf[key] + self[key] = v # update atime + return v + + def __setitem__(self, key, value): + self.shelf[key] = time.time(), value + + def __delitem__(self, key): + try: + del self.shelf[key] + except KeyError: + pass + + def cleanup(self, timeout): + now = time.time() + for k in self.shelf.keys(): + atime, v = self.shelf[k] + if now - atime > timeout : + del self[k] + +if __name__ == '__main__' : + import doctest + doctest.testmod() diff --git a/pyulib/src/ulib/ext/web/session.pyc b/pyulib/src/ulib/ext/web/session.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec8850e8701fc42ba614e1bba67361b1ab6beb01 GIT binary patch literal 14357 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 Ro;`W=`qA&szBP%O{{@O&sdNAU literal 0 HcmV?d00001 diff --git a/pyulib/src/ulib/ext/web/template.py b/pyulib/src/ulib/ext/web/template.py new file mode 100644 index 0000000..bffb7da --- /dev/null +++ b/pyulib/src/ulib/ext/web/template.py @@ -0,0 +1,1447 @@ +""" +simple, elegant templating +(part of web.py) + +Template design: + +Template string is split into tokens and the tokens are combined into nodes. +Parse tree is a nodelist. TextNode and ExpressionNode are simple nodes and +for-loop, if-loop etc are block nodes, which contain multiple child nodes. + +Each node can emit some python string. python string emitted by the +root node is validated for safeeval and executed using python in the given environment. + +Enough care is taken to make sure the generated code and the template has line to line match, +so that the error messages can point to exact line number in template. (It doesn't work in some cases still.) + +Grammar: + + template -> defwith sections + defwith -> '$def with (' arguments ')' | '' + sections -> section* + section -> block | assignment | line + + assignment -> '$ ' + line -> (text|expr)* + text -> + expr -> '$' pyexpr | '$(' pyexpr ')' | '${' pyexpr '}' + pyexpr -> + +""" + +__all__ = [ + "Template", + "Render", "render", "frender", + "ParseError", "SecurityError", + "test" +] + +import tokenize +import os +import glob +import re + +from utils import storage, safeunicode, safestr, re_compile +from webapi import config +from net import websafe + +def splitline(text): + r""" + Splits the given text at newline. + + >>> splitline('foo\nbar') + ('foo\n', 'bar') + >>> splitline('foo') + ('foo', '') + >>> splitline('') + ('', '') + """ + index = text.find('\n') + 1 + if index: + return text[:index], text[index:] + else: + return text, '' + +class Parser: + """Parser Base. + """ + def __init__(self, text, name="